From 6a7e6e5657a0798e66ac77664ea374b63c1fc40d Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Mon, 11 Aug 2025 17:20:11 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E5=B9=B3=E5=8F=B0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=EF=BC=8Cglb=E6=A0=BC=E5=BC=8F=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=8A=A8=E7=94=BB=E6=92=AD=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/AugmentWebviewStateStore.xml | 2 +- QPanda3D/Panda3DWorld.py | 53 +- QPanda3D/QPanda3DWidget.py | 123 +- .../__pycache__/Panda3DWorld.cpython-312.pyc | Bin 5114 -> 6066 bytes .../QPanda3DWidget.cpython-312.pyc | Bin 10292 -> 12720 bytes RenderPipelineFile/config/daytime.yaml | 4 +- RenderPipelineFile/samples/06-Car/main.py | 2 +- .../gui_preview_window.cpython-312.pyc | Bin 0 -> 23679 bytes __pycache__/main.cpython-312.pyc | Bin 36451 -> 36709 bytes core/CustomMouseController.py | 96 + .../CustomMouseController.cpython-312.pyc | Bin 0 -> 6345 bytes core/__pycache__/selection.cpython-312.pyc | Bin 44806 -> 58493 bytes core/__pycache__/world.cpython-312.pyc | Bin 32599 -> 39205 bytes core/selection.py | 865 ++- core/world.py | 216 +- main.py | 3 + pandatool/CMakeLists.txt | 53 + pandatool/src/assimp/CMakeLists.txt | 39 + pandatool/src/assimp/assimpLoader.I | 12 + pandatool/src/assimp/assimpLoader.cxx | 1326 +++++ pandatool/src/assimp/assimpLoader.h | 94 + pandatool/src/assimp/config_assimp.cxx | 114 + pandatool/src/assimp/config_assimp.h | 40 + pandatool/src/assimp/loaderFileTypeAssimp.cxx | 133 + pandatool/src/assimp/loaderFileTypeAssimp.h | 57 + pandatool/src/assimp/p3assimp_composite1.cxx | 7 + pandatool/src/assimp/pandaIOStream.cxx | 107 + pandatool/src/assimp/pandaIOStream.h | 45 + pandatool/src/assimp/pandaIOSystem.cxx | 85 + pandatool/src/assimp/pandaIOSystem.h | 40 + pandatool/src/assimp/pandaLogger.cxx | 71 + pandatool/src/assimp/pandaLogger.h | 52 + pandatool/src/bam/CMakeLists.txt | 27 + pandatool/src/bam/bamInfo.cxx | 328 ++ pandatool/src/bam/bamInfo.h | 65 + pandatool/src/bam/bamToEgg.cxx | 104 + pandatool/src/bam/bamToEgg.h | 34 + pandatool/src/bam/eggToBam.cxx | 491 ++ pandatool/src/bam/eggToBam.h | 77 + pandatool/src/bam/ptsToBam.cxx | 238 + pandatool/src/bam/ptsToBam.h | 64 + pandatool/src/converter/CMakeLists.txt | 23 + .../src/converter/eggToSomethingConverter.I | 64 + .../src/converter/eggToSomethingConverter.cxx | 69 + .../src/converter/eggToSomethingConverter.h | 71 + .../src/converter/somethingToEggConverter.I | 391 ++ .../src/converter/somethingToEggConverter.cxx | 165 + .../src/converter/somethingToEggConverter.h | 148 + pandatool/src/daeegg/CMakeLists.txt | 26 + pandatool/src/daeegg/config_daeegg.cxx | 43 + pandatool/src/daeegg/config_daeegg.h | 24 + pandatool/src/daeegg/daeCharacter.cxx | 312 ++ pandatool/src/daeegg/daeCharacter.h | 95 + pandatool/src/daeegg/daeMaterials.cxx | 451 ++ pandatool/src/daeegg/daeMaterials.h | 101 + pandatool/src/daeegg/daeToEggConverter.cxx | 851 +++ pandatool/src/daeegg/daeToEggConverter.h | 87 + pandatool/src/daeegg/fcollada_utils.h | 38 + pandatool/src/daeegg/p3daeegg_composite1.cxx | 5 + pandatool/src/daeegg/pre_fcollada_include.h | 45 + pandatool/src/daeprogs/CMakeLists.txt | 11 + pandatool/src/daeprogs/daeToEgg.cxx | 82 + pandatool/src/daeprogs/daeToEgg.h | 35 + pandatool/src/daeprogs/eggToDAE.cxx | 173 + pandatool/src/daeprogs/eggToDAE.h | 43 + pandatool/src/deploy-stub/CMakeLists.txt | 42 + .../deploy-stub/NativeInvocationHandler.java | 25 + pandatool/src/deploy-stub/android_log.c | 53 + pandatool/src/deploy-stub/android_main.cxx | 337 ++ pandatool/src/deploy-stub/deploy-stub.c | 767 +++ pandatool/src/deploy-stub/frozen_dllmain.c | 134 + pandatool/src/dxf/CMakeLists.txt | 20 + pandatool/src/dxf/dxfFile.cxx | 937 ++++ pandatool/src/dxf/dxfFile.h | 168 + pandatool/src/dxf/dxfLayer.cxx | 29 + pandatool/src/dxf/dxfLayer.h | 34 + pandatool/src/dxf/dxfLayerMap.cxx | 38 + pandatool/src/dxf/dxfLayerMap.h | 33 + pandatool/src/dxf/dxfVertex.cxx | 31 + pandatool/src/dxf/dxfVertex.h | 38 + pandatool/src/dxf/p3dxf_composite1.cxx | 4 + pandatool/src/dxfegg/CMakeLists.txt | 19 + pandatool/src/dxfegg/dxfToEggConverter.cxx | 145 + pandatool/src/dxfegg/dxfToEggConverter.h | 48 + pandatool/src/dxfegg/dxfToEggLayer.cxx | 99 + pandatool/src/dxfegg/dxfToEggLayer.h | 48 + pandatool/src/dxfprogs/CMakeLists.txt | 19 + pandatool/src/dxfprogs/dxfPoints.cxx | 89 + pandatool/src/dxfprogs/dxfPoints.h | 41 + pandatool/src/dxfprogs/dxfToEgg.cxx | 74 + pandatool/src/dxfprogs/dxfToEgg.h | 32 + pandatool/src/dxfprogs/eggToDXF.cxx | 149 + pandatool/src/dxfprogs/eggToDXF.h | 43 + pandatool/src/dxfprogs/eggToDXFLayer.cxx | 219 + pandatool/src/dxfprogs/eggToDXFLayer.h | 56 + pandatool/src/egg-mkfont/CMakeLists.txt | 25 + .../src/egg-mkfont/egg-mkfont_composite1.cxx | 5 + pandatool/src/egg-mkfont/eggMakeFont.cxx | 745 +++ pandatool/src/egg-mkfont/eggMakeFont.h | 100 + pandatool/src/egg-mkfont/rangeDescription.I | 61 + pandatool/src/egg-mkfont/rangeDescription.cxx | 175 + pandatool/src/egg-mkfont/rangeDescription.h | 60 + pandatool/src/egg-mkfont/rangeIterator.I | 29 + pandatool/src/egg-mkfont/rangeIterator.cxx | 63 + pandatool/src/egg-mkfont/rangeIterator.h | 45 + pandatool/src/egg-optchar/CMakeLists.txt | 27 + .../src/egg-optchar/config_egg_optchar.cxx | 41 + .../src/egg-optchar/config_egg_optchar.h | 21 + pandatool/src/egg-optchar/eggOptchar.cxx | 1522 ++++++ pandatool/src/egg-optchar/eggOptchar.h | 129 + .../src/egg-optchar/eggOptcharUserData.I | 79 + .../src/egg-optchar/eggOptcharUserData.cxx | 16 + .../src/egg-optchar/eggOptcharUserData.h | 69 + pandatool/src/egg-optchar/vertexMembership.I | 52 + .../src/egg-optchar/vertexMembership.cxx | 14 + pandatool/src/egg-optchar/vertexMembership.h | 43 + pandatool/src/egg-palettize/CMakeLists.txt | 20 + pandatool/src/egg-palettize/eggPalettize.cxx | 878 +++ pandatool/src/egg-palettize/eggPalettize.h | 71 + pandatool/src/egg-palettize/txaFileFilter.I | 12 + pandatool/src/egg-palettize/txaFileFilter.cxx | 148 + pandatool/src/egg-palettize/txaFileFilter.h | 72 + pandatool/src/egg-qtess/CMakeLists.txt | 35 + pandatool/src/egg-qtess/config_egg_qtess.cxx | 22 + pandatool/src/egg-qtess/config_egg_qtess.h | 24 + .../src/egg-qtess/egg-qtess_composite1.cxx | 8 + pandatool/src/egg-qtess/eggQtess.cxx | 337 ++ pandatool/src/egg-qtess/eggQtess.h | 54 + pandatool/src/egg-qtess/isoPlacer.I | 28 + pandatool/src/egg-qtess/isoPlacer.cxx | 228 + pandatool/src/egg-qtess/isoPlacer.h | 43 + pandatool/src/egg-qtess/qtessGlobals.cxx | 19 + pandatool/src/egg-qtess/qtessGlobals.h | 31 + pandatool/src/egg-qtess/qtessInputEntry.I | 156 + pandatool/src/egg-qtess/qtessInputEntry.cxx | 465 ++ pandatool/src/egg-qtess/qtessInputEntry.h | 90 + pandatool/src/egg-qtess/qtessInputFile.I | 29 + pandatool/src/egg-qtess/qtessInputFile.cxx | 327 ++ pandatool/src/egg-qtess/qtessInputFile.h | 54 + pandatool/src/egg-qtess/qtessSurface.I | 154 + pandatool/src/egg-qtess/qtessSurface.cxx | 565 ++ pandatool/src/egg-qtess/qtessSurface.h | 112 + pandatool/src/egg-qtess/subdivSegment.I | 48 + pandatool/src/egg-qtess/subdivSegment.cxx | 79 + pandatool/src/egg-qtess/subdivSegment.h | 43 + pandatool/src/eggbase/CMakeLists.txt | 33 + pandatool/src/eggbase/eggBase.cxx | 405 ++ pandatool/src/eggbase/eggBase.h | 78 + pandatool/src/eggbase/eggConverter.cxx | 34 + pandatool/src/eggbase/eggConverter.h | 36 + pandatool/src/eggbase/eggFilter.cxx | 72 + pandatool/src/eggbase/eggFilter.h | 35 + pandatool/src/eggbase/eggMakeSomething.cxx | 25 + pandatool/src/eggbase/eggMakeSomething.h | 30 + pandatool/src/eggbase/eggMultiBase.cxx | 187 + pandatool/src/eggbase/eggMultiBase.h | 49 + pandatool/src/eggbase/eggMultiFilter.cxx | 210 + pandatool/src/eggbase/eggMultiFilter.h | 51 + pandatool/src/eggbase/eggReader.cxx | 375 ++ pandatool/src/eggbase/eggReader.h | 62 + pandatool/src/eggbase/eggSingleBase.cxx | 70 + pandatool/src/eggbase/eggSingleBase.h | 51 + pandatool/src/eggbase/eggToSomething.cxx | 155 + pandatool/src/eggbase/eggToSomething.h | 44 + pandatool/src/eggbase/eggWriter.cxx | 224 + pandatool/src/eggbase/eggWriter.h | 45 + .../src/eggbase/p3eggbase_composite1.cxx | 13 + pandatool/src/eggbase/somethingToEgg.cxx | 328 ++ pandatool/src/eggbase/somethingToEgg.h | 76 + pandatool/src/eggcharbase/CMakeLists.txt | 46 + .../src/eggcharbase/config_eggcharbase.cxx | 61 + .../src/eggcharbase/config_eggcharbase.h | 24 + pandatool/src/eggcharbase/eggBackPointer.cxx | 61 + pandatool/src/eggcharbase/eggBackPointer.h | 60 + .../src/eggcharbase/eggCharacterCollection.I | 94 + .../eggcharbase/eggCharacterCollection.cxx | 680 +++ .../src/eggcharbase/eggCharacterCollection.h | 117 + pandatool/src/eggcharbase/eggCharacterData.I | 150 + .../src/eggcharbase/eggCharacterData.cxx | 410 ++ pandatool/src/eggcharbase/eggCharacterData.h | 121 + pandatool/src/eggcharbase/eggCharacterDb.I | 37 + pandatool/src/eggcharbase/eggCharacterDb.cxx | 125 + pandatool/src/eggcharbase/eggCharacterDb.h | 84 + .../src/eggcharbase/eggCharacterFilter.cxx | 108 + .../src/eggcharbase/eggCharacterFilter.h | 49 + pandatool/src/eggcharbase/eggComponentData.I | 47 + .../src/eggcharbase/eggComponentData.cxx | 128 + pandatool/src/eggcharbase/eggComponentData.h | 89 + pandatool/src/eggcharbase/eggJointData.I | 93 + pandatool/src/eggcharbase/eggJointData.cxx | 769 +++ pandatool/src/eggcharbase/eggJointData.h | 126 + .../src/eggcharbase/eggJointNodePointer.cxx | 209 + .../src/eggcharbase/eggJointNodePointer.h | 69 + pandatool/src/eggcharbase/eggJointPointer.I | 12 + pandatool/src/eggcharbase/eggJointPointer.cxx | 89 + pandatool/src/eggcharbase/eggJointPointer.h | 71 + .../src/eggcharbase/eggMatrixTablePointer.cxx | 295 + .../src/eggcharbase/eggMatrixTablePointer.h | 75 + .../src/eggcharbase/eggScalarTablePointer.cxx | 97 + .../src/eggcharbase/eggScalarTablePointer.h | 61 + pandatool/src/eggcharbase/eggSliderData.I | 12 + pandatool/src/eggcharbase/eggSliderData.cxx | 102 + pandatool/src/eggcharbase/eggSliderData.h | 59 + .../src/eggcharbase/eggSliderPointer.cxx | 16 + pandatool/src/eggcharbase/eggSliderPointer.h | 49 + .../src/eggcharbase/eggVertexPointer.cxx | 51 + pandatool/src/eggcharbase/eggVertexPointer.h | 55 + .../eggcharbase/p3eggcharbase_composite1.cxx | 17 + pandatool/src/eggprogs/CMakeLists.txt | 43 + pandatool/src/eggprogs/eggCrop.cxx | 124 + pandatool/src/eggprogs/eggCrop.h | 41 + pandatool/src/eggprogs/eggListTextures.cxx | 65 + pandatool/src/eggprogs/eggListTextures.h | 31 + pandatool/src/eggprogs/eggMakeTube.cxx | 270 + pandatool/src/eggprogs/eggMakeTube.h | 55 + pandatool/src/eggprogs/eggRename.cxx | 58 + pandatool/src/eggprogs/eggRename.h | 34 + pandatool/src/eggprogs/eggRetargetAnim.cxx | 192 + pandatool/src/eggprogs/eggRetargetAnim.h | 47 + pandatool/src/eggprogs/eggTextureCards.cxx | 492 ++ pandatool/src/eggprogs/eggTextureCards.h | 74 + pandatool/src/eggprogs/eggToC.cxx | 373 ++ pandatool/src/eggprogs/eggToC.h | 56 + pandatool/src/eggprogs/eggTopstrip.cxx | 349 ++ pandatool/src/eggprogs/eggTopstrip.h | 58 + pandatool/src/eggprogs/eggTrans.cxx | 137 + pandatool/src/eggprogs/eggTrans.h | 40 + pandatool/src/flt/CMakeLists.txt | 90 + pandatool/src/flt/config_flt.cxx | 112 + pandatool/src/flt/config_flt.h | 30 + pandatool/src/flt/fltBead.cxx | 357 ++ pandatool/src/flt/fltBead.h | 87 + pandatool/src/flt/fltBeadID.cxx | 124 + pandatool/src/flt/fltBeadID.h | 61 + pandatool/src/flt/fltCurve.I | 32 + pandatool/src/flt/fltCurve.cxx | 88 + pandatool/src/flt/fltCurve.h | 71 + pandatool/src/flt/fltError.cxx | 55 + pandatool/src/flt/fltError.h | 37 + pandatool/src/flt/fltExternalReference.cxx | 136 + pandatool/src/flt/fltExternalReference.h | 73 + pandatool/src/flt/fltEyepoint.cxx | 135 + pandatool/src/flt/fltEyepoint.h | 55 + pandatool/src/flt/fltFace.I | 12 + pandatool/src/flt/fltFace.cxx | 67 + pandatool/src/flt/fltFace.h | 53 + pandatool/src/flt/fltGeometry.I | 87 + pandatool/src/flt/fltGeometry.cxx | 262 + pandatool/src/flt/fltGeometry.h | 139 + pandatool/src/flt/fltGroup.cxx | 88 + pandatool/src/flt/fltGroup.h | 64 + pandatool/src/flt/fltHeader.cxx | 1703 ++++++ pandatool/src/flt/fltHeader.h | 340 ++ pandatool/src/flt/fltInstanceDefinition.cxx | 67 + pandatool/src/flt/fltInstanceDefinition.h | 59 + pandatool/src/flt/fltInstanceRef.cxx | 112 + pandatool/src/flt/fltInstanceRef.h | 61 + pandatool/src/flt/fltLOD.cxx | 91 + pandatool/src/flt/fltLOD.h | 64 + .../src/flt/fltLightSourceDefinition.cxx | 129 + pandatool/src/flt/fltLightSourceDefinition.h | 81 + pandatool/src/flt/fltLocalVertexPool.I | 12 + pandatool/src/flt/fltLocalVertexPool.cxx | 234 + pandatool/src/flt/fltLocalVertexPool.h | 77 + pandatool/src/flt/fltMaterial.cxx | 164 + pandatool/src/flt/fltMaterial.h | 74 + pandatool/src/flt/fltMesh.I | 12 + pandatool/src/flt/fltMesh.cxx | 110 + pandatool/src/flt/fltMesh.h | 60 + pandatool/src/flt/fltMeshPrimitive.I | 12 + pandatool/src/flt/fltMeshPrimitive.cxx | 126 + pandatool/src/flt/fltMeshPrimitive.h | 69 + pandatool/src/flt/fltObject.cxx | 76 + pandatool/src/flt/fltObject.h | 65 + pandatool/src/flt/fltOpcode.cxx | 297 + pandatool/src/flt/fltOpcode.h | 122 + pandatool/src/flt/fltPackedColor.I | 72 + pandatool/src/flt/fltPackedColor.cxx | 54 + pandatool/src/flt/fltPackedColor.h | 53 + pandatool/src/flt/fltRecord.I | 18 + pandatool/src/flt/fltRecord.cxx | 746 +++ pandatool/src/flt/fltRecord.h | 123 + pandatool/src/flt/fltRecordReader.cxx | 242 + pandatool/src/flt/fltRecordReader.h | 67 + pandatool/src/flt/fltRecordWriter.cxx | 151 + pandatool/src/flt/fltRecordWriter.h | 57 + pandatool/src/flt/fltTexture.cxx | 475 ++ pandatool/src/flt/fltTexture.h | 252 + pandatool/src/flt/fltTrackplane.cxx | 97 + pandatool/src/flt/fltTrackplane.h | 49 + .../src/flt/fltTransformGeneralMatrix.cxx | 88 + pandatool/src/flt/fltTransformGeneralMatrix.h | 54 + pandatool/src/flt/fltTransformPut.cxx | 195 + pandatool/src/flt/fltTransformPut.h | 75 + pandatool/src/flt/fltTransformRecord.cxx | 33 + pandatool/src/flt/fltTransformRecord.h | 57 + .../src/flt/fltTransformRotateAboutEdge.cxx | 144 + .../src/flt/fltTransformRotateAboutEdge.h | 64 + .../src/flt/fltTransformRotateAboutPoint.cxx | 140 + .../src/flt/fltTransformRotateAboutPoint.h | 64 + pandatool/src/flt/fltTransformRotateScale.cxx | 208 + pandatool/src/flt/fltTransformRotateScale.h | 71 + pandatool/src/flt/fltTransformScale.cxx | 142 + pandatool/src/flt/fltTransformScale.h | 62 + pandatool/src/flt/fltTransformTranslate.cxx | 122 + pandatool/src/flt/fltTransformTranslate.h | 61 + pandatool/src/flt/fltUnsupportedRecord.cxx | 61 + pandatool/src/flt/fltUnsupportedRecord.h | 60 + pandatool/src/flt/fltVectorRecord.cxx | 77 + pandatool/src/flt/fltVectorRecord.h | 61 + pandatool/src/flt/fltVertex.I | 32 + pandatool/src/flt/fltVertex.cxx | 259 + pandatool/src/flt/fltVertex.h | 91 + pandatool/src/flt/fltVertexList.cxx | 116 + pandatool/src/flt/fltVertexList.h | 65 + pandatool/src/flt/p3flt_composite1.cxx | 41 + pandatool/src/fltegg/CMakeLists.txt | 19 + pandatool/src/fltegg/fltToEggConverter.I | 12 + pandatool/src/fltegg/fltToEggConverter.cxx | 839 +++ pandatool/src/fltegg/fltToEggConverter.h | 111 + pandatool/src/fltegg/fltToEggLevelState.I | 46 + pandatool/src/fltegg/fltToEggLevelState.cxx | 229 + pandatool/src/fltegg/fltToEggLevelState.h | 64 + pandatool/src/fltprogs/CMakeLists.txt | 23 + pandatool/src/fltprogs/eggToFlt.cxx | 672 +++ pandatool/src/fltprogs/eggToFlt.h | 70 + pandatool/src/fltprogs/fltInfo.cxx | 97 + pandatool/src/fltprogs/fltInfo.h | 41 + pandatool/src/fltprogs/fltToEgg.cxx | 106 + pandatool/src/fltprogs/fltToEgg.h | 36 + pandatool/src/fltprogs/fltTrans.cxx | 133 + pandatool/src/fltprogs/fltTrans.h | 40 + pandatool/src/gtk-stats/CMakeLists.txt | 47 + pandatool/src/gtk-stats/gtkStats.cxx | 31 + pandatool/src/gtk-stats/gtkStats.h | 19 + pandatool/src/gtk-stats/gtkStatsChartMenu.cxx | 328 ++ pandatool/src/gtk-stats/gtkStatsChartMenu.h | 68 + .../src/gtk-stats/gtkStatsFlameGraph.cxx | 814 +++ pandatool/src/gtk-stats/gtkStatsFlameGraph.h | 92 + pandatool/src/gtk-stats/gtkStatsGraph.cxx | 702 +++ pandatool/src/gtk-stats/gtkStatsGraph.h | 170 + pandatool/src/gtk-stats/gtkStatsLabel.cxx | 283 + pandatool/src/gtk-stats/gtkStatsLabel.h | 89 + .../src/gtk-stats/gtkStatsLabelStack.cxx | 143 + pandatool/src/gtk-stats/gtkStatsLabelStack.h | 56 + pandatool/src/gtk-stats/gtkStatsMonitor.I | 47 + pandatool/src/gtk-stats/gtkStatsMonitor.cxx | 882 +++ pandatool/src/gtk-stats/gtkStatsMonitor.h | 148 + pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx | 642 +++ pandatool/src/gtk-stats/gtkStatsPianoRoll.h | 79 + pandatool/src/gtk-stats/gtkStatsServer.cxx | 766 +++ pandatool/src/gtk-stats/gtkStatsServer.h | 80 + .../src/gtk-stats/gtkStatsStripChart.cxx | 796 +++ pandatool/src/gtk-stats/gtkStatsStripChart.h | 94 + pandatool/src/gtk-stats/gtkStatsTimeline.cxx | 929 ++++ pandatool/src/gtk-stats/gtkStatsTimeline.h | 101 + .../src/gtk-stats/gtkstats_composite1.cxx | 11 + pandatool/src/imagebase/CMakeLists.txt | 20 + pandatool/src/imagebase/imageBase.cxx | 30 + pandatool/src/imagebase/imageBase.h | 42 + pandatool/src/imagebase/imageFilter.cxx | 42 + pandatool/src/imagebase/imageFilter.h | 34 + pandatool/src/imagebase/imageReader.cxx | 46 + pandatool/src/imagebase/imageReader.h | 34 + pandatool/src/imagebase/imageWriter.I | 20 + pandatool/src/imagebase/imageWriter.cxx | 80 + pandatool/src/imagebase/imageWriter.h | 40 + .../src/imagebase/p3imagebase_composite1.cxx | 6 + pandatool/src/imageprogs/CMakeLists.txt | 23 + .../src/imageprogs/imageFixHiddenColor.I | 12 + .../src/imageprogs/imageFixHiddenColor.cxx | 149 + .../src/imageprogs/imageFixHiddenColor.h | 38 + pandatool/src/imageprogs/imageInfo.cxx | 94 + pandatool/src/imageprogs/imageInfo.h | 41 + pandatool/src/imageprogs/imageResize.I | 99 + pandatool/src/imageprogs/imageResize.cxx | 123 + pandatool/src/imageprogs/imageResize.h | 68 + pandatool/src/imageprogs/imageTrans.cxx | 209 + pandatool/src/imageprogs/imageTrans.h | 53 + .../src/imageprogs/imageTransformColors.I | 12 + .../src/imageprogs/imageTransformColors.cxx | 481 ++ .../src/imageprogs/imageTransformColors.h | 63 + pandatool/src/lwo/CMakeLists.txt | 69 + pandatool/src/lwo/config_lwo.cxx | 112 + pandatool/src/lwo/config_lwo.h | 21 + pandatool/src/lwo/iffChunk.I | 35 + pandatool/src/lwo/iffChunk.cxx | 44 + pandatool/src/lwo/iffChunk.h | 72 + pandatool/src/lwo/iffGenericChunk.I | 35 + pandatool/src/lwo/iffGenericChunk.cxx | 43 + pandatool/src/lwo/iffGenericChunk.h | 61 + pandatool/src/lwo/iffId.I | 84 + pandatool/src/lwo/iffId.cxx | 41 + pandatool/src/lwo/iffId.h | 55 + pandatool/src/lwo/iffInputFile.I | 57 + pandatool/src/lwo/iffInputFile.cxx | 369 ++ pandatool/src/lwo/iffInputFile.h | 98 + pandatool/src/lwo/lwoBoundingBox.cxx | 45 + pandatool/src/lwo/lwoBoundingBox.h | 53 + pandatool/src/lwo/lwoChunk.cxx | 16 + pandatool/src/lwo/lwoChunk.h | 48 + pandatool/src/lwo/lwoClip.cxx | 61 + pandatool/src/lwo/lwoClip.h | 53 + .../src/lwo/lwoDiscontinuousVertexMap.cxx | 124 + pandatool/src/lwo/lwoDiscontinuousVertexMap.h | 64 + pandatool/src/lwo/lwoGroupChunk.cxx | 82 + pandatool/src/lwo/lwoGroupChunk.h | 61 + pandatool/src/lwo/lwoHeader.I | 29 + pandatool/src/lwo/lwoHeader.cxx | 72 + pandatool/src/lwo/lwoHeader.h | 61 + pandatool/src/lwo/lwoInputFile.I | 31 + pandatool/src/lwo/lwoInputFile.cxx | 138 + pandatool/src/lwo/lwoInputFile.h | 64 + pandatool/src/lwo/lwoLayer.cxx | 71 + pandatool/src/lwo/lwoLayer.h | 64 + pandatool/src/lwo/lwoPoints.cxx | 64 + pandatool/src/lwo/lwoPoints.h | 57 + pandatool/src/lwo/lwoPolygonTags.cxx | 80 + pandatool/src/lwo/lwoPolygonTags.h | 59 + pandatool/src/lwo/lwoPolygons.cxx | 120 + pandatool/src/lwo/lwoPolygons.h | 83 + pandatool/src/lwo/lwoStillImage.cxx | 44 + pandatool/src/lwo/lwoStillImage.h | 52 + pandatool/src/lwo/lwoSurface.cxx | 88 + pandatool/src/lwo/lwoSurface.h | 54 + pandatool/src/lwo/lwoSurfaceBlock.cxx | 104 + pandatool/src/lwo/lwoSurfaceBlock.h | 38 + pandatool/src/lwo/lwoSurfaceBlockAxis.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockAxis.h | 57 + pandatool/src/lwo/lwoSurfaceBlockChannel.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockChannel.h | 51 + pandatool/src/lwo/lwoSurfaceBlockCoordSys.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockCoordSys.h | 56 + pandatool/src/lwo/lwoSurfaceBlockEnabled.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockEnabled.h | 51 + pandatool/src/lwo/lwoSurfaceBlockHeader.cxx | 85 + pandatool/src/lwo/lwoSurfaceBlockHeader.h | 52 + pandatool/src/lwo/lwoSurfaceBlockImage.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockImage.h | 51 + pandatool/src/lwo/lwoSurfaceBlockOpacity.cxx | 48 + pandatool/src/lwo/lwoSurfaceBlockOpacity.h | 63 + .../src/lwo/lwoSurfaceBlockProjection.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockProjection.h | 60 + pandatool/src/lwo/lwoSurfaceBlockRefObj.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockRefObj.h | 51 + pandatool/src/lwo/lwoSurfaceBlockRepeat.cxx | 46 + pandatool/src/lwo/lwoSurfaceBlockRepeat.h | 54 + pandatool/src/lwo/lwoSurfaceBlockTMap.cxx | 72 + pandatool/src/lwo/lwoSurfaceBlockTMap.h | 49 + .../src/lwo/lwoSurfaceBlockTransform.cxx | 46 + pandatool/src/lwo/lwoSurfaceBlockTransform.h | 56 + pandatool/src/lwo/lwoSurfaceBlockVMapName.cxx | 44 + pandatool/src/lwo/lwoSurfaceBlockVMapName.h | 51 + pandatool/src/lwo/lwoSurfaceBlockWrap.cxx | 46 + pandatool/src/lwo/lwoSurfaceBlockWrap.h | 56 + pandatool/src/lwo/lwoSurfaceColor.cxx | 46 + pandatool/src/lwo/lwoSurfaceColor.h | 53 + pandatool/src/lwo/lwoSurfaceParameter.cxx | 46 + pandatool/src/lwo/lwoSurfaceParameter.h | 53 + pandatool/src/lwo/lwoSurfaceSidedness.cxx | 44 + pandatool/src/lwo/lwoSurfaceSidedness.h | 56 + .../src/lwo/lwoSurfaceSmoothingAngle.cxx | 45 + pandatool/src/lwo/lwoSurfaceSmoothingAngle.h | 51 + pandatool/src/lwo/lwoTags.cxx | 75 + pandatool/src/lwo/lwoTags.h | 62 + pandatool/src/lwo/lwoVertexMap.cxx | 88 + pandatool/src/lwo/lwoVertexMap.h | 62 + pandatool/src/lwo/p3lwo_composite1.cxx | 41 + pandatool/src/lwo/test_lwo.cxx | 42 + pandatool/src/lwoegg/CMakeLists.txt | 31 + pandatool/src/lwoegg/cLwoClip.I | 31 + pandatool/src/lwoegg/cLwoClip.cxx | 43 + pandatool/src/lwoegg/cLwoClip.h | 45 + pandatool/src/lwoegg/cLwoLayer.I | 31 + pandatool/src/lwoegg/cLwoLayer.cxx | 52 + pandatool/src/lwoegg/cLwoLayer.h | 44 + pandatool/src/lwoegg/cLwoPoints.I | 24 + pandatool/src/lwoegg/cLwoPoints.cxx | 97 + pandatool/src/lwoegg/cLwoPoints.h | 58 + pandatool/src/lwoegg/cLwoPolygons.I | 26 + pandatool/src/lwoegg/cLwoPolygons.cxx | 284 + pandatool/src/lwoegg/cLwoPolygons.h | 73 + pandatool/src/lwoegg/cLwoSurface.I | 42 + pandatool/src/lwoegg/cLwoSurface.cxx | 495 ++ pandatool/src/lwoegg/cLwoSurface.h | 109 + pandatool/src/lwoegg/cLwoSurfaceBlock.I | 12 + pandatool/src/lwoegg/cLwoSurfaceBlock.cxx | 148 + pandatool/src/lwoegg/cLwoSurfaceBlock.h | 66 + pandatool/src/lwoegg/cLwoSurfaceBlockTMap.I | 12 + pandatool/src/lwoegg/cLwoSurfaceBlockTMap.cxx | 73 + pandatool/src/lwoegg/cLwoSurfaceBlockTMap.h | 50 + pandatool/src/lwoegg/lwoToEggConverter.I | 12 + pandatool/src/lwoegg/lwoToEggConverter.cxx | 443 ++ pandatool/src/lwoegg/lwoToEggConverter.h | 92 + pandatool/src/lwoegg/p3lwoegg_composite1.cxx | 11 + pandatool/src/lwoprogs/CMakeLists.txt | 15 + pandatool/src/lwoprogs/lwoScan.cxx | 84 + pandatool/src/lwoprogs/lwoScan.h | 35 + pandatool/src/lwoprogs/lwoToEgg.cxx | 87 + pandatool/src/lwoprogs/lwoToEgg.h | 34 + pandatool/src/mac-stats/CMakeLists.txt | 57 + pandatool/src/mac-stats/Info.plist | 47 + pandatool/src/mac-stats/cocoa_compat.h | 45 + pandatool/src/mac-stats/macStats.h | 19 + pandatool/src/mac-stats/macStats.mm | 73 + pandatool/src/mac-stats/macStatsAppDelegate.h | 37 + .../src/mac-stats/macStatsAppDelegate.mm | 201 + pandatool/src/mac-stats/macStatsChartMenu.h | 60 + pandatool/src/mac-stats/macStatsChartMenu.mm | 246 + .../src/mac-stats/macStatsChartMenuDelegate.h | 32 + .../mac-stats/macStatsChartMenuDelegate.mm | 61 + pandatool/src/mac-stats/macStatsFlameGraph.h | 89 + pandatool/src/mac-stats/macStatsFlameGraph.mm | 919 ++++ pandatool/src/mac-stats/macStatsGraph.h | 139 + pandatool/src/mac-stats/macStatsGraph.mm | 491 ++ pandatool/src/mac-stats/macStatsGraphView.h | 31 + pandatool/src/mac-stats/macStatsGraphView.mm | 156 + .../mac-stats/macStatsGraphViewController.h | 42 + .../mac-stats/macStatsGraphViewController.mm | 224 + pandatool/src/mac-stats/macStatsLabel.h | 58 + pandatool/src/mac-stats/macStatsLabel.mm | 137 + pandatool/src/mac-stats/macStatsLabelStack.h | 57 + pandatool/src/mac-stats/macStatsLabelStack.mm | 174 + pandatool/src/mac-stats/macStatsMonitor.h | 117 + pandatool/src/mac-stats/macStatsMonitor.mm | 647 +++ pandatool/src/mac-stats/macStatsPianoRoll.h | 84 + pandatool/src/mac-stats/macStatsPianoRoll.mm | 764 +++ pandatool/src/mac-stats/macStatsScaleArea.h | 41 + pandatool/src/mac-stats/macStatsScaleArea.mm | 50 + pandatool/src/mac-stats/macStatsServer.h | 82 + pandatool/src/mac-stats/macStatsServer.mm | 732 +++ pandatool/src/mac-stats/macStatsStripChart.h | 90 + pandatool/src/mac-stats/macStatsStripChart.mm | 964 ++++ pandatool/src/mac-stats/macStatsTimeline.h | 100 + pandatool/src/mac-stats/macStatsTimeline.mm | 949 ++++ .../src/mac-stats/macstats_composite1.mm | 16 + pandatool/src/miscprogs/CMakeLists.txt | 7 + pandatool/src/miscprogs/binToC.cxx | 149 + pandatool/src/miscprogs/binToC.h | 42 + pandatool/src/objegg/CMakeLists.txt | 23 + pandatool/src/objegg/config_objegg.cxx | 37 + pandatool/src/objegg/config_objegg.h | 24 + pandatool/src/objegg/eggToObjConverter.cxx | 411 ++ pandatool/src/objegg/eggToObjConverter.h | 75 + pandatool/src/objegg/objToEggConverter.I | 53 + pandatool/src/objegg/objToEggConverter.cxx | 1106 ++++ pandatool/src/objegg/objToEggConverter.h | 149 + pandatool/src/objprogs/CMakeLists.txt | 15 + pandatool/src/objprogs/eggToObj.cxx | 82 + pandatool/src/objprogs/eggToObj.h | 37 + pandatool/src/objprogs/objToEgg.cxx | 74 + pandatool/src/objprogs/objToEgg.h | 32 + pandatool/src/palettizer/CMakeLists.txt | 60 + .../src/palettizer/config_palettizer.cxx | 82 + pandatool/src/palettizer/config_palettizer.h | 21 + pandatool/src/palettizer/destTextureImage.cxx | 161 + pandatool/src/palettizer/destTextureImage.h | 74 + pandatool/src/palettizer/eggFile.cxx | 808 +++ pandatool/src/palettizer/eggFile.h | 137 + pandatool/src/palettizer/filenameUnifier.cxx | 133 + pandatool/src/palettizer/filenameUnifier.h | 51 + pandatool/src/palettizer/imageFile.cxx | 480 ++ pandatool/src/palettizer/imageFile.h | 100 + pandatool/src/palettizer/omitReason.cxx | 48 + pandatool/src/palettizer/omitReason.h | 57 + .../palettizer/p3palettizer_composite1.cxx | 24 + pandatool/src/palettizer/pal_string_utils.cxx | 86 + pandatool/src/palettizer/pal_string_utils.h | 27 + pandatool/src/palettizer/paletteGroup.cxx | 713 +++ pandatool/src/palettizer/paletteGroup.h | 151 + pandatool/src/palettizer/paletteGroups.cxx | 343 ++ pandatool/src/palettizer/paletteGroups.h | 111 + pandatool/src/palettizer/paletteImage.cxx | 1073 ++++ pandatool/src/palettizer/paletteImage.h | 145 + pandatool/src/palettizer/palettePage.cxx | 299 ++ pandatool/src/palettizer/palettePage.h | 99 + pandatool/src/palettizer/palettizer.cxx | 1175 ++++ pandatool/src/palettizer/palettizer.h | 190 + .../src/palettizer/sourceTextureImage.cxx | 209 + pandatool/src/palettizer/sourceTextureImage.h | 85 + pandatool/src/palettizer/textureImage.cxx | 1396 +++++ pandatool/src/palettizer/textureImage.h | 195 + .../src/palettizer/textureMemoryCounter.cxx | 250 + .../src/palettizer/textureMemoryCounter.h | 67 + pandatool/src/palettizer/texturePlacement.cxx | 1148 ++++ pandatool/src/palettizer/texturePlacement.h | 157 + pandatool/src/palettizer/texturePosition.cxx | 135 + pandatool/src/palettizer/texturePosition.h | 75 + .../src/palettizer/textureProperties.cxx | 938 ++++ pandatool/src/palettizer/textureProperties.h | 117 + pandatool/src/palettizer/textureReference.cxx | 901 ++++ pandatool/src/palettizer/textureReference.h | 143 + pandatool/src/palettizer/textureRequest.cxx | 50 + pandatool/src/palettizer/textureRequest.h | 55 + pandatool/src/palettizer/txaFile.cxx | 598 +++ pandatool/src/palettizer/txaFile.h | 61 + pandatool/src/palettizer/txaLine.cxx | 621 +++ pandatool/src/palettizer/txaLine.h | 102 + pandatool/src/pandatoolbase/CMakeLists.txt | 36 + .../src/pandatoolbase/animationConvert.cxx | 91 + .../src/pandatoolbase/animationConvert.h | 39 + .../pandatoolbase/config_pandatoolbase.cxx | 31 + .../src/pandatoolbase/config_pandatoolbase.h | 25 + pandatool/src/pandatoolbase/distanceUnit.cxx | 215 + pandatool/src/pandatoolbase/distanceUnit.h | 45 + .../p3pandatoolbase_composite1.cxx | 6 + pandatool/src/pandatoolbase/pandatoolbase.cxx | 14 + pandatool/src/pandatoolbase/pandatoolbase.h | 24 + .../src/pandatoolbase/pandatoolsymbols.h | 35 + pandatool/src/pandatoolbase/pathReplace.I | 147 + pandatool/src/pandatoolbase/pathReplace.cxx | 554 ++ pandatool/src/pandatoolbase/pathReplace.h | 127 + pandatool/src/pandatoolbase/pathStore.cxx | 81 + pandatool/src/pandatoolbase/pathStore.h | 37 + pandatool/src/pfmprogs/CMakeLists.txt | 13 + pandatool/src/pfmprogs/config_pfmprogs.cxx | 43 + pandatool/src/pfmprogs/config_pfmprogs.h | 29 + pandatool/src/pfmprogs/pfmBba.cxx | 142 + pandatool/src/pfmprogs/pfmBba.h | 48 + pandatool/src/pfmprogs/pfmTrans.cxx | 512 ++ pandatool/src/pfmprogs/pfmTrans.h | 83 + pandatool/src/progbase/CMakeLists.txt | 27 + .../src/progbase/p3progbase_composite1.cxx | 7 + pandatool/src/progbase/programBase.I | 20 + pandatool/src/progbase/programBase.cxx | 1493 ++++++ pandatool/src/progbase/programBase.h | 171 + pandatool/src/progbase/test_prog.cxx | 69 + pandatool/src/progbase/withOutputFile.I | 21 + pandatool/src/progbase/withOutputFile.cxx | 200 + pandatool/src/progbase/withOutputFile.h | 62 + pandatool/src/progbase/wordWrapStream.cxx | 25 + pandatool/src/progbase/wordWrapStream.h | 38 + pandatool/src/progbase/wordWrapStreamBuf.I | 26 + pandatool/src/progbase/wordWrapStreamBuf.cxx | 114 + pandatool/src/progbase/wordWrapStreamBuf.h | 50 + pandatool/src/pstatserver/CMakeLists.txt | 46 + .../pstatserver/p3pstatserver_composite1.cxx | 13 + pandatool/src/pstatserver/pStatClientData.cxx | 560 ++ pandatool/src/pstatserver/pStatClientData.h | 119 + pandatool/src/pstatserver/pStatFlameGraph.I | 137 + pandatool/src/pstatserver/pStatFlameGraph.cxx | 773 +++ pandatool/src/pstatserver/pStatFlameGraph.h | 150 + pandatool/src/pstatserver/pStatGraph.I | 131 + pandatool/src/pstatserver/pStatGraph.cxx | 371 ++ pandatool/src/pstatserver/pStatGraph.h | 130 + pandatool/src/pstatserver/pStatListener.cxx | 50 + pandatool/src/pstatserver/pStatListener.h | 42 + pandatool/src/pstatserver/pStatMonitor.I | 95 + pandatool/src/pstatserver/pStatMonitor.cxx | 799 +++ pandatool/src/pstatserver/pStatMonitor.h | 143 + pandatool/src/pstatserver/pStatPianoRoll.I | 76 + pandatool/src/pstatserver/pStatPianoRoll.cxx | 360 ++ pandatool/src/pstatserver/pStatPianoRoll.h | 108 + pandatool/src/pstatserver/pStatReader.cxx | 340 ++ pandatool/src/pstatserver/pStatReader.h | 87 + pandatool/src/pstatserver/pStatServer.cxx | 306 ++ pandatool/src/pstatserver/pStatServer.h | 89 + pandatool/src/pstatserver/pStatStripChart.I | 186 + pandatool/src/pstatserver/pStatStripChart.cxx | 1128 ++++ pandatool/src/pstatserver/pStatStripChart.h | 158 + pandatool/src/pstatserver/pStatThreadData.I | 28 + pandatool/src/pstatserver/pStatThreadData.cxx | 462 ++ pandatool/src/pstatserver/pStatThreadData.h | 92 + pandatool/src/pstatserver/pStatTimeline.I | 151 + pandatool/src/pstatserver/pStatTimeline.cxx | 915 ++++ pandatool/src/pstatserver/pStatTimeline.h | 138 + pandatool/src/pstatserver/pStatView.I | 70 + pandatool/src/pstatserver/pStatView.cxx | 550 ++ pandatool/src/pstatserver/pStatView.h | 80 + pandatool/src/pstatserver/pStatViewLevel.I | 37 + pandatool/src/pstatserver/pStatViewLevel.cxx | 82 + pandatool/src/pstatserver/pStatViewLevel.h | 55 + pandatool/src/ptloader/CMakeLists.txt | 31 + pandatool/src/ptloader/config_ptloader.cxx | 116 + pandatool/src/ptloader/config_ptloader.h | 32 + .../src/ptloader/loaderFileTypePandatool.cxx | 223 + .../src/ptloader/loaderFileTypePandatool.h | 71 + pandatool/src/text-stats/CMakeLists.txt | 23 + pandatool/src/text-stats/textMonitor.I | 12 + pandatool/src/text-stats/textMonitor.cxx | 247 + pandatool/src/text-stats/textMonitor.h | 57 + pandatool/src/text-stats/textStats.cxx | 120 + pandatool/src/text-stats/textStats.h | 48 + pandatool/src/vrml/CMakeLists.txt | 30 + pandatool/src/vrml/parse_vrml.cxx | 140 + pandatool/src/vrml/parse_vrml.h | 23 + pandatool/src/vrml/standardNodes.wrl | 488 ++ pandatool/src/vrml/standardNodes.wrl.c | 1342 +++++ pandatool/src/vrml/standardNodes.wrl.pz.c | 273 + pandatool/src/vrml/standard_nodes.cxx | 47 + pandatool/src/vrml/standard_nodes.h | 26 + pandatool/src/vrml/vrmlLexer.cxx.prebuilt | 4766 +++++++++++++++++ pandatool/src/vrml/vrmlLexer.lxx | 581 ++ pandatool/src/vrml/vrmlLexerDefs.h | 28 + pandatool/src/vrml/vrmlNode.cxx | 80 + pandatool/src/vrml/vrmlNode.h | 70 + pandatool/src/vrml/vrmlNodeType.cxx | 338 ++ pandatool/src/vrml/vrmlNodeType.h | 108 + pandatool/src/vrml/vrmlParser.cxx.prebuilt | 2387 +++++++++ pandatool/src/vrml/vrmlParser.h.prebuilt | 138 + pandatool/src/vrml/vrmlParser.yxx | 567 ++ pandatool/src/vrml/vrmlParserDefs.h | 23 + pandatool/src/vrmlegg/CMakeLists.txt | 22 + pandatool/src/vrmlegg/indexedFaceSet.cxx | 549 ++ pandatool/src/vrmlegg/indexedFaceSet.h | 83 + pandatool/src/vrmlegg/vrmlAppearance.cxx | 67 + pandatool/src/vrmlegg/vrmlAppearance.h | 39 + pandatool/src/vrmlegg/vrmlToEggConverter.cxx | 382 ++ pandatool/src/vrmlegg/vrmlToEggConverter.h | 66 + pandatool/src/vrmlprogs/CMakeLists.txt | 15 + pandatool/src/vrmlprogs/vrmlToEgg.cxx | 72 + pandatool/src/vrmlprogs/vrmlToEgg.h | 32 + pandatool/src/vrmlprogs/vrmlTrans.cxx | 94 + pandatool/src/vrmlprogs/vrmlTrans.h | 38 + pandatool/src/win-stats/CMakeLists.txt | 45 + pandatool/src/win-stats/winStats.cxx | 66 + pandatool/src/win-stats/winStats.h | 19 + pandatool/src/win-stats/winStatsChartMenu.cxx | 279 + pandatool/src/win-stats/winStatsChartMenu.h | 56 + .../src/win-stats/winStatsFlameGraph.cxx | 868 +++ pandatool/src/win-stats/winStatsFlameGraph.h | 87 + pandatool/src/win-stats/winStatsGraph.cxx | 831 +++ pandatool/src/win-stats/winStatsGraph.h | 164 + pandatool/src/win-stats/winStatsLabel.I | 68 + pandatool/src/win-stats/winStatsLabel.cxx | 390 ++ pandatool/src/win-stats/winStatsLabel.h | 97 + .../src/win-stats/winStatsLabelStack.cxx | 465 ++ pandatool/src/win-stats/winStatsLabelStack.h | 91 + pandatool/src/win-stats/winStatsMenuId.h | 53 + pandatool/src/win-stats/winStatsMonitor.I | 41 + pandatool/src/win-stats/winStatsMonitor.cxx | 952 ++++ pandatool/src/win-stats/winStatsMonitor.h | 143 + pandatool/src/win-stats/winStatsPianoRoll.cxx | 699 +++ pandatool/src/win-stats/winStatsPianoRoll.h | 88 + pandatool/src/win-stats/winStatsServer.cxx | 941 ++++ pandatool/src/win-stats/winStatsServer.h | 82 + .../src/win-stats/winStatsStripChart.cxx | 900 ++++ pandatool/src/win-stats/winStatsStripChart.h | 98 + pandatool/src/win-stats/winStatsTimeline.cxx | 805 +++ pandatool/src/win-stats/winStatsTimeline.h | 95 + .../src/win-stats/winstats_composite1.cxx | 11 + pandatool/src/xfile/CMakeLists.txt | 56 + pandatool/src/xfile/config_xfile.cxx | 68 + pandatool/src/xfile/config_xfile.h | 27 + pandatool/src/xfile/p3xfile_composite1.cxx | 17 + pandatool/src/xfile/standardTemplates.x | 274 + pandatool/src/xfile/standardTemplates.x.c | 515 ++ pandatool/src/xfile/standardTemplates.x.pz.c | 184 + pandatool/src/xfile/standard_templates.cxx | 48 + pandatool/src/xfile/standard_templates.h | 26 + pandatool/src/xfile/windowsGuid.I | 98 + pandatool/src/xfile/windowsGuid.cxx | 76 + pandatool/src/xfile/windowsGuid.h | 58 + pandatool/src/xfile/xFile.I | 12 + pandatool/src/xfile/xFile.cxx | 479 ++ pandatool/src/xfile/xFile.h | 104 + pandatool/src/xfile/xFileArrayDef.I | 60 + pandatool/src/xfile/xFileArrayDef.cxx | 75 + pandatool/src/xfile/xFileArrayDef.h | 49 + pandatool/src/xfile/xFileDataDef.I | 61 + pandatool/src/xfile/xFileDataDef.cxx | 496 ++ pandatool/src/xfile/xFileDataDef.h | 137 + pandatool/src/xfile/xFileDataNode.I | 45 + pandatool/src/xfile/xFileDataNode.cxx | 65 + pandatool/src/xfile/xFileDataNode.h | 72 + pandatool/src/xfile/xFileDataNodeReference.I | 20 + .../src/xfile/xFileDataNodeReference.cxx | 94 + pandatool/src/xfile/xFileDataNodeReference.h | 69 + pandatool/src/xfile/xFileDataNodeTemplate.I | 12 + pandatool/src/xfile/xFileDataNodeTemplate.cxx | 228 + pandatool/src/xfile/xFileDataNodeTemplate.h | 81 + pandatool/src/xfile/xFileDataObject.I | 304 ++ pandatool/src/xfile/xFileDataObject.cxx | 306 ++ pandatool/src/xfile/xFileDataObject.h | 133 + pandatool/src/xfile/xFileDataObjectArray.I | 21 + pandatool/src/xfile/xFileDataObjectArray.cxx | 103 + pandatool/src/xfile/xFileDataObjectArray.h | 62 + pandatool/src/xfile/xFileDataObjectDouble.I | 12 + pandatool/src/xfile/xFileDataObjectDouble.cxx | 91 + pandatool/src/xfile/xFileDataObjectDouble.h | 63 + pandatool/src/xfile/xFileDataObjectInteger.I | 12 + .../src/xfile/xFileDataObjectInteger.cxx | 77 + pandatool/src/xfile/xFileDataObjectInteger.h | 62 + pandatool/src/xfile/xFileDataObjectString.I | 12 + pandatool/src/xfile/xFileDataObjectString.cxx | 97 + pandatool/src/xfile/xFileDataObjectString.h | 61 + pandatool/src/xfile/xFileNode.I | 70 + pandatool/src/xfile/xFileNode.cxx | 499 ++ pandatool/src/xfile/xFileNode.h | 139 + pandatool/src/xfile/xFileParseData.I | 12 + pandatool/src/xfile/xFileParseData.cxx | 39 + pandatool/src/xfile/xFileParseData.h | 70 + pandatool/src/xfile/xFileTemplate.I | 70 + pandatool/src/xfile/xFileTemplate.cxx | 131 + pandatool/src/xfile/xFileTemplate.h | 81 + pandatool/src/xfile/xLexer.cxx.prebuilt | 2637 +++++++++ pandatool/src/xfile/xLexer.lxx | 624 +++ pandatool/src/xfile/xLexerDefs.h | 34 + pandatool/src/xfile/xParser.cxx.prebuilt | 2166 ++++++++ pandatool/src/xfile/xParser.h.prebuilt | 118 + pandatool/src/xfile/xParser.yxx | 464 ++ pandatool/src/xfile/xParserDefs.h | 53 + pandatool/src/xfileegg/CMakeLists.txt | 26 + .../src/xfileegg/p3xfileegg_composite1.cxx | 11 + pandatool/src/xfileegg/xFileAnimationSet.I | 46 + pandatool/src/xfileegg/xFileAnimationSet.cxx | 153 + pandatool/src/xfileegg/xFileAnimationSet.h | 92 + pandatool/src/xfileegg/xFileFace.cxx | 43 + pandatool/src/xfileegg/xFileFace.h | 42 + pandatool/src/xfileegg/xFileMaker.cxx | 243 + pandatool/src/xfileegg/xFileMaker.h | 64 + pandatool/src/xfileegg/xFileMaterial.cxx | 209 + pandatool/src/xfileegg/xFileMaterial.h | 58 + pandatool/src/xfileegg/xFileMesh.cxx | 884 +++ pandatool/src/xfileegg/xFileMesh.h | 126 + pandatool/src/xfileegg/xFileNormal.cxx | 63 + pandatool/src/xfileegg/xFileNormal.h | 38 + .../src/xfileegg/xFileToEggConverter.cxx | 745 +++ pandatool/src/xfileegg/xFileToEggConverter.h | 117 + pandatool/src/xfileegg/xFileVertex.cxx | 88 + pandatool/src/xfileegg/xFileVertex.h | 39 + pandatool/src/xfileegg/xFileVertexPool.h | 51 + pandatool/src/xfileprogs/CMakeLists.txt | 19 + pandatool/src/xfileprogs/eggToX.cxx | 77 + pandatool/src/xfileprogs/eggToX.h | 44 + pandatool/src/xfileprogs/xFileToEgg.cxx | 121 + pandatool/src/xfileprogs/xFileToEgg.h | 40 + pandatool/src/xfileprogs/xFileTrans.cxx | 96 + pandatool/src/xfileprogs/xFileTrans.h | 38 + .../__pycache__/scene_manager.cpython-312.pyc | Bin 41770 -> 41956 bytes scene/__pycache__/util.cpython-312.pyc | Bin 0 -> 12036 bytes scene/scene_manager.py | 7 +- scene/util.py | 300 ++ ui/__pycache__/property_panel.cpython-312.pyc | Bin 177132 -> 187523 bytes ui/__pycache__/widgets.cpython-312.pyc | Bin 19523 -> 19645 bytes ui/property_panel.py | 333 +- ui/widgets.py | 1 + 837 files changed, 137428 insertions(+), 404 deletions(-) create mode 100644 __pycache__/gui_preview_window.cpython-312.pyc create mode 100644 core/CustomMouseController.py create mode 100644 core/__pycache__/CustomMouseController.cpython-312.pyc create mode 100644 pandatool/CMakeLists.txt create mode 100644 pandatool/src/assimp/CMakeLists.txt create mode 100644 pandatool/src/assimp/assimpLoader.I create mode 100644 pandatool/src/assimp/assimpLoader.cxx create mode 100644 pandatool/src/assimp/assimpLoader.h create mode 100644 pandatool/src/assimp/config_assimp.cxx create mode 100644 pandatool/src/assimp/config_assimp.h create mode 100644 pandatool/src/assimp/loaderFileTypeAssimp.cxx create mode 100644 pandatool/src/assimp/loaderFileTypeAssimp.h create mode 100644 pandatool/src/assimp/p3assimp_composite1.cxx create mode 100644 pandatool/src/assimp/pandaIOStream.cxx create mode 100644 pandatool/src/assimp/pandaIOStream.h create mode 100644 pandatool/src/assimp/pandaIOSystem.cxx create mode 100644 pandatool/src/assimp/pandaIOSystem.h create mode 100644 pandatool/src/assimp/pandaLogger.cxx create mode 100644 pandatool/src/assimp/pandaLogger.h create mode 100644 pandatool/src/bam/CMakeLists.txt create mode 100644 pandatool/src/bam/bamInfo.cxx create mode 100644 pandatool/src/bam/bamInfo.h create mode 100644 pandatool/src/bam/bamToEgg.cxx create mode 100644 pandatool/src/bam/bamToEgg.h create mode 100644 pandatool/src/bam/eggToBam.cxx create mode 100644 pandatool/src/bam/eggToBam.h create mode 100644 pandatool/src/bam/ptsToBam.cxx create mode 100644 pandatool/src/bam/ptsToBam.h create mode 100644 pandatool/src/converter/CMakeLists.txt create mode 100644 pandatool/src/converter/eggToSomethingConverter.I create mode 100644 pandatool/src/converter/eggToSomethingConverter.cxx create mode 100644 pandatool/src/converter/eggToSomethingConverter.h create mode 100644 pandatool/src/converter/somethingToEggConverter.I create mode 100644 pandatool/src/converter/somethingToEggConverter.cxx create mode 100644 pandatool/src/converter/somethingToEggConverter.h create mode 100644 pandatool/src/daeegg/CMakeLists.txt create mode 100644 pandatool/src/daeegg/config_daeegg.cxx create mode 100644 pandatool/src/daeegg/config_daeegg.h create mode 100644 pandatool/src/daeegg/daeCharacter.cxx create mode 100644 pandatool/src/daeegg/daeCharacter.h create mode 100644 pandatool/src/daeegg/daeMaterials.cxx create mode 100644 pandatool/src/daeegg/daeMaterials.h create mode 100644 pandatool/src/daeegg/daeToEggConverter.cxx create mode 100644 pandatool/src/daeegg/daeToEggConverter.h create mode 100644 pandatool/src/daeegg/fcollada_utils.h create mode 100644 pandatool/src/daeegg/p3daeegg_composite1.cxx create mode 100644 pandatool/src/daeegg/pre_fcollada_include.h create mode 100644 pandatool/src/daeprogs/CMakeLists.txt create mode 100644 pandatool/src/daeprogs/daeToEgg.cxx create mode 100644 pandatool/src/daeprogs/daeToEgg.h create mode 100644 pandatool/src/daeprogs/eggToDAE.cxx create mode 100644 pandatool/src/daeprogs/eggToDAE.h create mode 100644 pandatool/src/deploy-stub/CMakeLists.txt create mode 100644 pandatool/src/deploy-stub/NativeInvocationHandler.java create mode 100644 pandatool/src/deploy-stub/android_log.c create mode 100644 pandatool/src/deploy-stub/android_main.cxx create mode 100644 pandatool/src/deploy-stub/deploy-stub.c create mode 100644 pandatool/src/deploy-stub/frozen_dllmain.c create mode 100644 pandatool/src/dxf/CMakeLists.txt create mode 100644 pandatool/src/dxf/dxfFile.cxx create mode 100644 pandatool/src/dxf/dxfFile.h create mode 100644 pandatool/src/dxf/dxfLayer.cxx create mode 100644 pandatool/src/dxf/dxfLayer.h create mode 100644 pandatool/src/dxf/dxfLayerMap.cxx create mode 100644 pandatool/src/dxf/dxfLayerMap.h create mode 100644 pandatool/src/dxf/dxfVertex.cxx create mode 100644 pandatool/src/dxf/dxfVertex.h create mode 100644 pandatool/src/dxf/p3dxf_composite1.cxx create mode 100644 pandatool/src/dxfegg/CMakeLists.txt create mode 100644 pandatool/src/dxfegg/dxfToEggConverter.cxx create mode 100644 pandatool/src/dxfegg/dxfToEggConverter.h create mode 100644 pandatool/src/dxfegg/dxfToEggLayer.cxx create mode 100644 pandatool/src/dxfegg/dxfToEggLayer.h create mode 100644 pandatool/src/dxfprogs/CMakeLists.txt create mode 100644 pandatool/src/dxfprogs/dxfPoints.cxx create mode 100644 pandatool/src/dxfprogs/dxfPoints.h create mode 100644 pandatool/src/dxfprogs/dxfToEgg.cxx create mode 100644 pandatool/src/dxfprogs/dxfToEgg.h create mode 100644 pandatool/src/dxfprogs/eggToDXF.cxx create mode 100644 pandatool/src/dxfprogs/eggToDXF.h create mode 100644 pandatool/src/dxfprogs/eggToDXFLayer.cxx create mode 100644 pandatool/src/dxfprogs/eggToDXFLayer.h create mode 100644 pandatool/src/egg-mkfont/CMakeLists.txt create mode 100644 pandatool/src/egg-mkfont/egg-mkfont_composite1.cxx create mode 100644 pandatool/src/egg-mkfont/eggMakeFont.cxx create mode 100644 pandatool/src/egg-mkfont/eggMakeFont.h create mode 100644 pandatool/src/egg-mkfont/rangeDescription.I create mode 100644 pandatool/src/egg-mkfont/rangeDescription.cxx create mode 100644 pandatool/src/egg-mkfont/rangeDescription.h create mode 100644 pandatool/src/egg-mkfont/rangeIterator.I create mode 100644 pandatool/src/egg-mkfont/rangeIterator.cxx create mode 100644 pandatool/src/egg-mkfont/rangeIterator.h create mode 100644 pandatool/src/egg-optchar/CMakeLists.txt create mode 100644 pandatool/src/egg-optchar/config_egg_optchar.cxx create mode 100644 pandatool/src/egg-optchar/config_egg_optchar.h create mode 100644 pandatool/src/egg-optchar/eggOptchar.cxx create mode 100644 pandatool/src/egg-optchar/eggOptchar.h create mode 100644 pandatool/src/egg-optchar/eggOptcharUserData.I create mode 100644 pandatool/src/egg-optchar/eggOptcharUserData.cxx create mode 100644 pandatool/src/egg-optchar/eggOptcharUserData.h create mode 100644 pandatool/src/egg-optchar/vertexMembership.I create mode 100644 pandatool/src/egg-optchar/vertexMembership.cxx create mode 100644 pandatool/src/egg-optchar/vertexMembership.h create mode 100644 pandatool/src/egg-palettize/CMakeLists.txt create mode 100644 pandatool/src/egg-palettize/eggPalettize.cxx create mode 100644 pandatool/src/egg-palettize/eggPalettize.h create mode 100644 pandatool/src/egg-palettize/txaFileFilter.I create mode 100644 pandatool/src/egg-palettize/txaFileFilter.cxx create mode 100644 pandatool/src/egg-palettize/txaFileFilter.h create mode 100644 pandatool/src/egg-qtess/CMakeLists.txt create mode 100644 pandatool/src/egg-qtess/config_egg_qtess.cxx create mode 100644 pandatool/src/egg-qtess/config_egg_qtess.h create mode 100644 pandatool/src/egg-qtess/egg-qtess_composite1.cxx create mode 100644 pandatool/src/egg-qtess/eggQtess.cxx create mode 100644 pandatool/src/egg-qtess/eggQtess.h create mode 100644 pandatool/src/egg-qtess/isoPlacer.I create mode 100644 pandatool/src/egg-qtess/isoPlacer.cxx create mode 100644 pandatool/src/egg-qtess/isoPlacer.h create mode 100644 pandatool/src/egg-qtess/qtessGlobals.cxx create mode 100644 pandatool/src/egg-qtess/qtessGlobals.h create mode 100644 pandatool/src/egg-qtess/qtessInputEntry.I create mode 100644 pandatool/src/egg-qtess/qtessInputEntry.cxx create mode 100644 pandatool/src/egg-qtess/qtessInputEntry.h create mode 100644 pandatool/src/egg-qtess/qtessInputFile.I create mode 100644 pandatool/src/egg-qtess/qtessInputFile.cxx create mode 100644 pandatool/src/egg-qtess/qtessInputFile.h create mode 100644 pandatool/src/egg-qtess/qtessSurface.I create mode 100644 pandatool/src/egg-qtess/qtessSurface.cxx create mode 100644 pandatool/src/egg-qtess/qtessSurface.h create mode 100644 pandatool/src/egg-qtess/subdivSegment.I create mode 100644 pandatool/src/egg-qtess/subdivSegment.cxx create mode 100644 pandatool/src/egg-qtess/subdivSegment.h create mode 100644 pandatool/src/eggbase/CMakeLists.txt create mode 100644 pandatool/src/eggbase/eggBase.cxx create mode 100644 pandatool/src/eggbase/eggBase.h create mode 100644 pandatool/src/eggbase/eggConverter.cxx create mode 100644 pandatool/src/eggbase/eggConverter.h create mode 100644 pandatool/src/eggbase/eggFilter.cxx create mode 100644 pandatool/src/eggbase/eggFilter.h create mode 100644 pandatool/src/eggbase/eggMakeSomething.cxx create mode 100644 pandatool/src/eggbase/eggMakeSomething.h create mode 100644 pandatool/src/eggbase/eggMultiBase.cxx create mode 100644 pandatool/src/eggbase/eggMultiBase.h create mode 100644 pandatool/src/eggbase/eggMultiFilter.cxx create mode 100644 pandatool/src/eggbase/eggMultiFilter.h create mode 100644 pandatool/src/eggbase/eggReader.cxx create mode 100644 pandatool/src/eggbase/eggReader.h create mode 100644 pandatool/src/eggbase/eggSingleBase.cxx create mode 100644 pandatool/src/eggbase/eggSingleBase.h create mode 100644 pandatool/src/eggbase/eggToSomething.cxx create mode 100644 pandatool/src/eggbase/eggToSomething.h create mode 100644 pandatool/src/eggbase/eggWriter.cxx create mode 100644 pandatool/src/eggbase/eggWriter.h create mode 100644 pandatool/src/eggbase/p3eggbase_composite1.cxx create mode 100644 pandatool/src/eggbase/somethingToEgg.cxx create mode 100644 pandatool/src/eggbase/somethingToEgg.h create mode 100644 pandatool/src/eggcharbase/CMakeLists.txt create mode 100644 pandatool/src/eggcharbase/config_eggcharbase.cxx create mode 100644 pandatool/src/eggcharbase/config_eggcharbase.h create mode 100644 pandatool/src/eggcharbase/eggBackPointer.cxx create mode 100644 pandatool/src/eggcharbase/eggBackPointer.h create mode 100644 pandatool/src/eggcharbase/eggCharacterCollection.I create mode 100644 pandatool/src/eggcharbase/eggCharacterCollection.cxx create mode 100644 pandatool/src/eggcharbase/eggCharacterCollection.h create mode 100644 pandatool/src/eggcharbase/eggCharacterData.I create mode 100644 pandatool/src/eggcharbase/eggCharacterData.cxx create mode 100644 pandatool/src/eggcharbase/eggCharacterData.h create mode 100644 pandatool/src/eggcharbase/eggCharacterDb.I create mode 100644 pandatool/src/eggcharbase/eggCharacterDb.cxx create mode 100644 pandatool/src/eggcharbase/eggCharacterDb.h create mode 100644 pandatool/src/eggcharbase/eggCharacterFilter.cxx create mode 100644 pandatool/src/eggcharbase/eggCharacterFilter.h create mode 100644 pandatool/src/eggcharbase/eggComponentData.I create mode 100644 pandatool/src/eggcharbase/eggComponentData.cxx create mode 100644 pandatool/src/eggcharbase/eggComponentData.h create mode 100644 pandatool/src/eggcharbase/eggJointData.I create mode 100644 pandatool/src/eggcharbase/eggJointData.cxx create mode 100644 pandatool/src/eggcharbase/eggJointData.h create mode 100644 pandatool/src/eggcharbase/eggJointNodePointer.cxx create mode 100644 pandatool/src/eggcharbase/eggJointNodePointer.h create mode 100644 pandatool/src/eggcharbase/eggJointPointer.I create mode 100644 pandatool/src/eggcharbase/eggJointPointer.cxx create mode 100644 pandatool/src/eggcharbase/eggJointPointer.h create mode 100644 pandatool/src/eggcharbase/eggMatrixTablePointer.cxx create mode 100644 pandatool/src/eggcharbase/eggMatrixTablePointer.h create mode 100644 pandatool/src/eggcharbase/eggScalarTablePointer.cxx create mode 100644 pandatool/src/eggcharbase/eggScalarTablePointer.h create mode 100644 pandatool/src/eggcharbase/eggSliderData.I create mode 100644 pandatool/src/eggcharbase/eggSliderData.cxx create mode 100644 pandatool/src/eggcharbase/eggSliderData.h create mode 100644 pandatool/src/eggcharbase/eggSliderPointer.cxx create mode 100644 pandatool/src/eggcharbase/eggSliderPointer.h create mode 100644 pandatool/src/eggcharbase/eggVertexPointer.cxx create mode 100644 pandatool/src/eggcharbase/eggVertexPointer.h create mode 100644 pandatool/src/eggcharbase/p3eggcharbase_composite1.cxx create mode 100644 pandatool/src/eggprogs/CMakeLists.txt create mode 100644 pandatool/src/eggprogs/eggCrop.cxx create mode 100644 pandatool/src/eggprogs/eggCrop.h create mode 100644 pandatool/src/eggprogs/eggListTextures.cxx create mode 100644 pandatool/src/eggprogs/eggListTextures.h create mode 100644 pandatool/src/eggprogs/eggMakeTube.cxx create mode 100644 pandatool/src/eggprogs/eggMakeTube.h create mode 100644 pandatool/src/eggprogs/eggRename.cxx create mode 100644 pandatool/src/eggprogs/eggRename.h create mode 100644 pandatool/src/eggprogs/eggRetargetAnim.cxx create mode 100644 pandatool/src/eggprogs/eggRetargetAnim.h create mode 100644 pandatool/src/eggprogs/eggTextureCards.cxx create mode 100644 pandatool/src/eggprogs/eggTextureCards.h create mode 100644 pandatool/src/eggprogs/eggToC.cxx create mode 100644 pandatool/src/eggprogs/eggToC.h create mode 100644 pandatool/src/eggprogs/eggTopstrip.cxx create mode 100644 pandatool/src/eggprogs/eggTopstrip.h create mode 100644 pandatool/src/eggprogs/eggTrans.cxx create mode 100644 pandatool/src/eggprogs/eggTrans.h create mode 100644 pandatool/src/flt/CMakeLists.txt create mode 100644 pandatool/src/flt/config_flt.cxx create mode 100644 pandatool/src/flt/config_flt.h create mode 100644 pandatool/src/flt/fltBead.cxx create mode 100644 pandatool/src/flt/fltBead.h create mode 100644 pandatool/src/flt/fltBeadID.cxx create mode 100644 pandatool/src/flt/fltBeadID.h create mode 100644 pandatool/src/flt/fltCurve.I create mode 100644 pandatool/src/flt/fltCurve.cxx create mode 100644 pandatool/src/flt/fltCurve.h create mode 100644 pandatool/src/flt/fltError.cxx create mode 100644 pandatool/src/flt/fltError.h create mode 100644 pandatool/src/flt/fltExternalReference.cxx create mode 100644 pandatool/src/flt/fltExternalReference.h create mode 100644 pandatool/src/flt/fltEyepoint.cxx create mode 100644 pandatool/src/flt/fltEyepoint.h create mode 100644 pandatool/src/flt/fltFace.I create mode 100644 pandatool/src/flt/fltFace.cxx create mode 100644 pandatool/src/flt/fltFace.h create mode 100644 pandatool/src/flt/fltGeometry.I create mode 100644 pandatool/src/flt/fltGeometry.cxx create mode 100644 pandatool/src/flt/fltGeometry.h create mode 100644 pandatool/src/flt/fltGroup.cxx create mode 100644 pandatool/src/flt/fltGroup.h create mode 100644 pandatool/src/flt/fltHeader.cxx create mode 100644 pandatool/src/flt/fltHeader.h create mode 100644 pandatool/src/flt/fltInstanceDefinition.cxx create mode 100644 pandatool/src/flt/fltInstanceDefinition.h create mode 100644 pandatool/src/flt/fltInstanceRef.cxx create mode 100644 pandatool/src/flt/fltInstanceRef.h create mode 100644 pandatool/src/flt/fltLOD.cxx create mode 100644 pandatool/src/flt/fltLOD.h create mode 100644 pandatool/src/flt/fltLightSourceDefinition.cxx create mode 100644 pandatool/src/flt/fltLightSourceDefinition.h create mode 100644 pandatool/src/flt/fltLocalVertexPool.I create mode 100644 pandatool/src/flt/fltLocalVertexPool.cxx create mode 100644 pandatool/src/flt/fltLocalVertexPool.h create mode 100644 pandatool/src/flt/fltMaterial.cxx create mode 100644 pandatool/src/flt/fltMaterial.h create mode 100644 pandatool/src/flt/fltMesh.I create mode 100644 pandatool/src/flt/fltMesh.cxx create mode 100644 pandatool/src/flt/fltMesh.h create mode 100644 pandatool/src/flt/fltMeshPrimitive.I create mode 100644 pandatool/src/flt/fltMeshPrimitive.cxx create mode 100644 pandatool/src/flt/fltMeshPrimitive.h create mode 100644 pandatool/src/flt/fltObject.cxx create mode 100644 pandatool/src/flt/fltObject.h create mode 100644 pandatool/src/flt/fltOpcode.cxx create mode 100644 pandatool/src/flt/fltOpcode.h create mode 100644 pandatool/src/flt/fltPackedColor.I create mode 100644 pandatool/src/flt/fltPackedColor.cxx create mode 100644 pandatool/src/flt/fltPackedColor.h create mode 100644 pandatool/src/flt/fltRecord.I create mode 100644 pandatool/src/flt/fltRecord.cxx create mode 100644 pandatool/src/flt/fltRecord.h create mode 100644 pandatool/src/flt/fltRecordReader.cxx create mode 100644 pandatool/src/flt/fltRecordReader.h create mode 100644 pandatool/src/flt/fltRecordWriter.cxx create mode 100644 pandatool/src/flt/fltRecordWriter.h create mode 100644 pandatool/src/flt/fltTexture.cxx create mode 100644 pandatool/src/flt/fltTexture.h create mode 100644 pandatool/src/flt/fltTrackplane.cxx create mode 100644 pandatool/src/flt/fltTrackplane.h create mode 100644 pandatool/src/flt/fltTransformGeneralMatrix.cxx create mode 100644 pandatool/src/flt/fltTransformGeneralMatrix.h create mode 100644 pandatool/src/flt/fltTransformPut.cxx create mode 100644 pandatool/src/flt/fltTransformPut.h create mode 100644 pandatool/src/flt/fltTransformRecord.cxx create mode 100644 pandatool/src/flt/fltTransformRecord.h create mode 100644 pandatool/src/flt/fltTransformRotateAboutEdge.cxx create mode 100644 pandatool/src/flt/fltTransformRotateAboutEdge.h create mode 100644 pandatool/src/flt/fltTransformRotateAboutPoint.cxx create mode 100644 pandatool/src/flt/fltTransformRotateAboutPoint.h create mode 100644 pandatool/src/flt/fltTransformRotateScale.cxx create mode 100644 pandatool/src/flt/fltTransformRotateScale.h create mode 100644 pandatool/src/flt/fltTransformScale.cxx create mode 100644 pandatool/src/flt/fltTransformScale.h create mode 100644 pandatool/src/flt/fltTransformTranslate.cxx create mode 100644 pandatool/src/flt/fltTransformTranslate.h create mode 100644 pandatool/src/flt/fltUnsupportedRecord.cxx create mode 100644 pandatool/src/flt/fltUnsupportedRecord.h create mode 100644 pandatool/src/flt/fltVectorRecord.cxx create mode 100644 pandatool/src/flt/fltVectorRecord.h create mode 100644 pandatool/src/flt/fltVertex.I create mode 100644 pandatool/src/flt/fltVertex.cxx create mode 100644 pandatool/src/flt/fltVertex.h create mode 100644 pandatool/src/flt/fltVertexList.cxx create mode 100644 pandatool/src/flt/fltVertexList.h create mode 100644 pandatool/src/flt/p3flt_composite1.cxx create mode 100644 pandatool/src/fltegg/CMakeLists.txt create mode 100644 pandatool/src/fltegg/fltToEggConverter.I create mode 100644 pandatool/src/fltegg/fltToEggConverter.cxx create mode 100644 pandatool/src/fltegg/fltToEggConverter.h create mode 100644 pandatool/src/fltegg/fltToEggLevelState.I create mode 100644 pandatool/src/fltegg/fltToEggLevelState.cxx create mode 100644 pandatool/src/fltegg/fltToEggLevelState.h create mode 100644 pandatool/src/fltprogs/CMakeLists.txt create mode 100644 pandatool/src/fltprogs/eggToFlt.cxx create mode 100644 pandatool/src/fltprogs/eggToFlt.h create mode 100644 pandatool/src/fltprogs/fltInfo.cxx create mode 100644 pandatool/src/fltprogs/fltInfo.h create mode 100644 pandatool/src/fltprogs/fltToEgg.cxx create mode 100644 pandatool/src/fltprogs/fltToEgg.h create mode 100644 pandatool/src/fltprogs/fltTrans.cxx create mode 100644 pandatool/src/fltprogs/fltTrans.h create mode 100644 pandatool/src/gtk-stats/CMakeLists.txt create mode 100644 pandatool/src/gtk-stats/gtkStats.cxx create mode 100644 pandatool/src/gtk-stats/gtkStats.h create mode 100644 pandatool/src/gtk-stats/gtkStatsChartMenu.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsChartMenu.h create mode 100644 pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsFlameGraph.h create mode 100644 pandatool/src/gtk-stats/gtkStatsGraph.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsGraph.h create mode 100644 pandatool/src/gtk-stats/gtkStatsLabel.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsLabel.h create mode 100644 pandatool/src/gtk-stats/gtkStatsLabelStack.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsLabelStack.h create mode 100644 pandatool/src/gtk-stats/gtkStatsMonitor.I create mode 100644 pandatool/src/gtk-stats/gtkStatsMonitor.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsMonitor.h create mode 100644 pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsPianoRoll.h create mode 100644 pandatool/src/gtk-stats/gtkStatsServer.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsServer.h create mode 100644 pandatool/src/gtk-stats/gtkStatsStripChart.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsStripChart.h create mode 100644 pandatool/src/gtk-stats/gtkStatsTimeline.cxx create mode 100644 pandatool/src/gtk-stats/gtkStatsTimeline.h create mode 100644 pandatool/src/gtk-stats/gtkstats_composite1.cxx create mode 100644 pandatool/src/imagebase/CMakeLists.txt create mode 100644 pandatool/src/imagebase/imageBase.cxx create mode 100644 pandatool/src/imagebase/imageBase.h create mode 100644 pandatool/src/imagebase/imageFilter.cxx create mode 100644 pandatool/src/imagebase/imageFilter.h create mode 100644 pandatool/src/imagebase/imageReader.cxx create mode 100644 pandatool/src/imagebase/imageReader.h create mode 100644 pandatool/src/imagebase/imageWriter.I create mode 100644 pandatool/src/imagebase/imageWriter.cxx create mode 100644 pandatool/src/imagebase/imageWriter.h create mode 100644 pandatool/src/imagebase/p3imagebase_composite1.cxx create mode 100644 pandatool/src/imageprogs/CMakeLists.txt create mode 100644 pandatool/src/imageprogs/imageFixHiddenColor.I create mode 100644 pandatool/src/imageprogs/imageFixHiddenColor.cxx create mode 100644 pandatool/src/imageprogs/imageFixHiddenColor.h create mode 100644 pandatool/src/imageprogs/imageInfo.cxx create mode 100644 pandatool/src/imageprogs/imageInfo.h create mode 100644 pandatool/src/imageprogs/imageResize.I create mode 100644 pandatool/src/imageprogs/imageResize.cxx create mode 100644 pandatool/src/imageprogs/imageResize.h create mode 100644 pandatool/src/imageprogs/imageTrans.cxx create mode 100644 pandatool/src/imageprogs/imageTrans.h create mode 100644 pandatool/src/imageprogs/imageTransformColors.I create mode 100644 pandatool/src/imageprogs/imageTransformColors.cxx create mode 100644 pandatool/src/imageprogs/imageTransformColors.h create mode 100644 pandatool/src/lwo/CMakeLists.txt create mode 100644 pandatool/src/lwo/config_lwo.cxx create mode 100644 pandatool/src/lwo/config_lwo.h create mode 100644 pandatool/src/lwo/iffChunk.I create mode 100644 pandatool/src/lwo/iffChunk.cxx create mode 100644 pandatool/src/lwo/iffChunk.h create mode 100644 pandatool/src/lwo/iffGenericChunk.I create mode 100644 pandatool/src/lwo/iffGenericChunk.cxx create mode 100644 pandatool/src/lwo/iffGenericChunk.h create mode 100644 pandatool/src/lwo/iffId.I create mode 100644 pandatool/src/lwo/iffId.cxx create mode 100644 pandatool/src/lwo/iffId.h create mode 100644 pandatool/src/lwo/iffInputFile.I create mode 100644 pandatool/src/lwo/iffInputFile.cxx create mode 100644 pandatool/src/lwo/iffInputFile.h create mode 100644 pandatool/src/lwo/lwoBoundingBox.cxx create mode 100644 pandatool/src/lwo/lwoBoundingBox.h create mode 100644 pandatool/src/lwo/lwoChunk.cxx create mode 100644 pandatool/src/lwo/lwoChunk.h create mode 100644 pandatool/src/lwo/lwoClip.cxx create mode 100644 pandatool/src/lwo/lwoClip.h create mode 100644 pandatool/src/lwo/lwoDiscontinuousVertexMap.cxx create mode 100644 pandatool/src/lwo/lwoDiscontinuousVertexMap.h create mode 100644 pandatool/src/lwo/lwoGroupChunk.cxx create mode 100644 pandatool/src/lwo/lwoGroupChunk.h create mode 100644 pandatool/src/lwo/lwoHeader.I create mode 100644 pandatool/src/lwo/lwoHeader.cxx create mode 100644 pandatool/src/lwo/lwoHeader.h create mode 100644 pandatool/src/lwo/lwoInputFile.I create mode 100644 pandatool/src/lwo/lwoInputFile.cxx create mode 100644 pandatool/src/lwo/lwoInputFile.h create mode 100644 pandatool/src/lwo/lwoLayer.cxx create mode 100644 pandatool/src/lwo/lwoLayer.h create mode 100644 pandatool/src/lwo/lwoPoints.cxx create mode 100644 pandatool/src/lwo/lwoPoints.h create mode 100644 pandatool/src/lwo/lwoPolygonTags.cxx create mode 100644 pandatool/src/lwo/lwoPolygonTags.h create mode 100644 pandatool/src/lwo/lwoPolygons.cxx create mode 100644 pandatool/src/lwo/lwoPolygons.h create mode 100644 pandatool/src/lwo/lwoStillImage.cxx create mode 100644 pandatool/src/lwo/lwoStillImage.h create mode 100644 pandatool/src/lwo/lwoSurface.cxx create mode 100644 pandatool/src/lwo/lwoSurface.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlock.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlock.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockAxis.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockAxis.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockChannel.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockChannel.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockCoordSys.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockCoordSys.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockEnabled.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockEnabled.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockHeader.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockHeader.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockImage.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockImage.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockOpacity.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockOpacity.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockProjection.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockProjection.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockRefObj.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockRefObj.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockRepeat.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockRepeat.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockTMap.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockTMap.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockTransform.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockTransform.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockVMapName.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockVMapName.h create mode 100644 pandatool/src/lwo/lwoSurfaceBlockWrap.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceBlockWrap.h create mode 100644 pandatool/src/lwo/lwoSurfaceColor.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceColor.h create mode 100644 pandatool/src/lwo/lwoSurfaceParameter.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceParameter.h create mode 100644 pandatool/src/lwo/lwoSurfaceSidedness.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceSidedness.h create mode 100644 pandatool/src/lwo/lwoSurfaceSmoothingAngle.cxx create mode 100644 pandatool/src/lwo/lwoSurfaceSmoothingAngle.h create mode 100644 pandatool/src/lwo/lwoTags.cxx create mode 100644 pandatool/src/lwo/lwoTags.h create mode 100644 pandatool/src/lwo/lwoVertexMap.cxx create mode 100644 pandatool/src/lwo/lwoVertexMap.h create mode 100644 pandatool/src/lwo/p3lwo_composite1.cxx create mode 100644 pandatool/src/lwo/test_lwo.cxx create mode 100644 pandatool/src/lwoegg/CMakeLists.txt create mode 100644 pandatool/src/lwoegg/cLwoClip.I create mode 100644 pandatool/src/lwoegg/cLwoClip.cxx create mode 100644 pandatool/src/lwoegg/cLwoClip.h create mode 100644 pandatool/src/lwoegg/cLwoLayer.I create mode 100644 pandatool/src/lwoegg/cLwoLayer.cxx create mode 100644 pandatool/src/lwoegg/cLwoLayer.h create mode 100644 pandatool/src/lwoegg/cLwoPoints.I create mode 100644 pandatool/src/lwoegg/cLwoPoints.cxx create mode 100644 pandatool/src/lwoegg/cLwoPoints.h create mode 100644 pandatool/src/lwoegg/cLwoPolygons.I create mode 100644 pandatool/src/lwoegg/cLwoPolygons.cxx create mode 100644 pandatool/src/lwoegg/cLwoPolygons.h create mode 100644 pandatool/src/lwoegg/cLwoSurface.I create mode 100644 pandatool/src/lwoegg/cLwoSurface.cxx create mode 100644 pandatool/src/lwoegg/cLwoSurface.h create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlock.I create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlock.cxx create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlock.h create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlockTMap.I create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlockTMap.cxx create mode 100644 pandatool/src/lwoegg/cLwoSurfaceBlockTMap.h create mode 100644 pandatool/src/lwoegg/lwoToEggConverter.I create mode 100644 pandatool/src/lwoegg/lwoToEggConverter.cxx create mode 100644 pandatool/src/lwoegg/lwoToEggConverter.h create mode 100644 pandatool/src/lwoegg/p3lwoegg_composite1.cxx create mode 100644 pandatool/src/lwoprogs/CMakeLists.txt create mode 100644 pandatool/src/lwoprogs/lwoScan.cxx create mode 100644 pandatool/src/lwoprogs/lwoScan.h create mode 100644 pandatool/src/lwoprogs/lwoToEgg.cxx create mode 100644 pandatool/src/lwoprogs/lwoToEgg.h create mode 100644 pandatool/src/mac-stats/CMakeLists.txt create mode 100644 pandatool/src/mac-stats/Info.plist create mode 100644 pandatool/src/mac-stats/cocoa_compat.h create mode 100644 pandatool/src/mac-stats/macStats.h create mode 100644 pandatool/src/mac-stats/macStats.mm create mode 100644 pandatool/src/mac-stats/macStatsAppDelegate.h create mode 100644 pandatool/src/mac-stats/macStatsAppDelegate.mm create mode 100644 pandatool/src/mac-stats/macStatsChartMenu.h create mode 100644 pandatool/src/mac-stats/macStatsChartMenu.mm create mode 100644 pandatool/src/mac-stats/macStatsChartMenuDelegate.h create mode 100644 pandatool/src/mac-stats/macStatsChartMenuDelegate.mm create mode 100644 pandatool/src/mac-stats/macStatsFlameGraph.h create mode 100644 pandatool/src/mac-stats/macStatsFlameGraph.mm create mode 100644 pandatool/src/mac-stats/macStatsGraph.h create mode 100644 pandatool/src/mac-stats/macStatsGraph.mm create mode 100644 pandatool/src/mac-stats/macStatsGraphView.h create mode 100644 pandatool/src/mac-stats/macStatsGraphView.mm create mode 100644 pandatool/src/mac-stats/macStatsGraphViewController.h create mode 100644 pandatool/src/mac-stats/macStatsGraphViewController.mm create mode 100644 pandatool/src/mac-stats/macStatsLabel.h create mode 100644 pandatool/src/mac-stats/macStatsLabel.mm create mode 100644 pandatool/src/mac-stats/macStatsLabelStack.h create mode 100644 pandatool/src/mac-stats/macStatsLabelStack.mm create mode 100644 pandatool/src/mac-stats/macStatsMonitor.h create mode 100644 pandatool/src/mac-stats/macStatsMonitor.mm create mode 100644 pandatool/src/mac-stats/macStatsPianoRoll.h create mode 100644 pandatool/src/mac-stats/macStatsPianoRoll.mm create mode 100644 pandatool/src/mac-stats/macStatsScaleArea.h create mode 100644 pandatool/src/mac-stats/macStatsScaleArea.mm create mode 100644 pandatool/src/mac-stats/macStatsServer.h create mode 100644 pandatool/src/mac-stats/macStatsServer.mm create mode 100644 pandatool/src/mac-stats/macStatsStripChart.h create mode 100644 pandatool/src/mac-stats/macStatsStripChart.mm create mode 100644 pandatool/src/mac-stats/macStatsTimeline.h create mode 100644 pandatool/src/mac-stats/macStatsTimeline.mm create mode 100644 pandatool/src/mac-stats/macstats_composite1.mm create mode 100644 pandatool/src/miscprogs/CMakeLists.txt create mode 100644 pandatool/src/miscprogs/binToC.cxx create mode 100644 pandatool/src/miscprogs/binToC.h create mode 100644 pandatool/src/objegg/CMakeLists.txt create mode 100644 pandatool/src/objegg/config_objegg.cxx create mode 100644 pandatool/src/objegg/config_objegg.h create mode 100644 pandatool/src/objegg/eggToObjConverter.cxx create mode 100644 pandatool/src/objegg/eggToObjConverter.h create mode 100644 pandatool/src/objegg/objToEggConverter.I create mode 100644 pandatool/src/objegg/objToEggConverter.cxx create mode 100644 pandatool/src/objegg/objToEggConverter.h create mode 100644 pandatool/src/objprogs/CMakeLists.txt create mode 100644 pandatool/src/objprogs/eggToObj.cxx create mode 100644 pandatool/src/objprogs/eggToObj.h create mode 100644 pandatool/src/objprogs/objToEgg.cxx create mode 100644 pandatool/src/objprogs/objToEgg.h create mode 100644 pandatool/src/palettizer/CMakeLists.txt create mode 100644 pandatool/src/palettizer/config_palettizer.cxx create mode 100644 pandatool/src/palettizer/config_palettizer.h create mode 100644 pandatool/src/palettizer/destTextureImage.cxx create mode 100644 pandatool/src/palettizer/destTextureImage.h create mode 100644 pandatool/src/palettizer/eggFile.cxx create mode 100644 pandatool/src/palettizer/eggFile.h create mode 100644 pandatool/src/palettizer/filenameUnifier.cxx create mode 100644 pandatool/src/palettizer/filenameUnifier.h create mode 100644 pandatool/src/palettizer/imageFile.cxx create mode 100644 pandatool/src/palettizer/imageFile.h create mode 100644 pandatool/src/palettizer/omitReason.cxx create mode 100644 pandatool/src/palettizer/omitReason.h create mode 100644 pandatool/src/palettizer/p3palettizer_composite1.cxx create mode 100644 pandatool/src/palettizer/pal_string_utils.cxx create mode 100644 pandatool/src/palettizer/pal_string_utils.h create mode 100644 pandatool/src/palettizer/paletteGroup.cxx create mode 100644 pandatool/src/palettizer/paletteGroup.h create mode 100644 pandatool/src/palettizer/paletteGroups.cxx create mode 100644 pandatool/src/palettizer/paletteGroups.h create mode 100644 pandatool/src/palettizer/paletteImage.cxx create mode 100644 pandatool/src/palettizer/paletteImage.h create mode 100644 pandatool/src/palettizer/palettePage.cxx create mode 100644 pandatool/src/palettizer/palettePage.h create mode 100644 pandatool/src/palettizer/palettizer.cxx create mode 100644 pandatool/src/palettizer/palettizer.h create mode 100644 pandatool/src/palettizer/sourceTextureImage.cxx create mode 100644 pandatool/src/palettizer/sourceTextureImage.h create mode 100644 pandatool/src/palettizer/textureImage.cxx create mode 100644 pandatool/src/palettizer/textureImage.h create mode 100644 pandatool/src/palettizer/textureMemoryCounter.cxx create mode 100644 pandatool/src/palettizer/textureMemoryCounter.h create mode 100644 pandatool/src/palettizer/texturePlacement.cxx create mode 100644 pandatool/src/palettizer/texturePlacement.h create mode 100644 pandatool/src/palettizer/texturePosition.cxx create mode 100644 pandatool/src/palettizer/texturePosition.h create mode 100644 pandatool/src/palettizer/textureProperties.cxx create mode 100644 pandatool/src/palettizer/textureProperties.h create mode 100644 pandatool/src/palettizer/textureReference.cxx create mode 100644 pandatool/src/palettizer/textureReference.h create mode 100644 pandatool/src/palettizer/textureRequest.cxx create mode 100644 pandatool/src/palettizer/textureRequest.h create mode 100644 pandatool/src/palettizer/txaFile.cxx create mode 100644 pandatool/src/palettizer/txaFile.h create mode 100644 pandatool/src/palettizer/txaLine.cxx create mode 100644 pandatool/src/palettizer/txaLine.h create mode 100644 pandatool/src/pandatoolbase/CMakeLists.txt create mode 100644 pandatool/src/pandatoolbase/animationConvert.cxx create mode 100644 pandatool/src/pandatoolbase/animationConvert.h create mode 100644 pandatool/src/pandatoolbase/config_pandatoolbase.cxx create mode 100644 pandatool/src/pandatoolbase/config_pandatoolbase.h create mode 100644 pandatool/src/pandatoolbase/distanceUnit.cxx create mode 100644 pandatool/src/pandatoolbase/distanceUnit.h create mode 100644 pandatool/src/pandatoolbase/p3pandatoolbase_composite1.cxx create mode 100644 pandatool/src/pandatoolbase/pandatoolbase.cxx create mode 100644 pandatool/src/pandatoolbase/pandatoolbase.h create mode 100644 pandatool/src/pandatoolbase/pandatoolsymbols.h create mode 100644 pandatool/src/pandatoolbase/pathReplace.I create mode 100644 pandatool/src/pandatoolbase/pathReplace.cxx create mode 100644 pandatool/src/pandatoolbase/pathReplace.h create mode 100644 pandatool/src/pandatoolbase/pathStore.cxx create mode 100644 pandatool/src/pandatoolbase/pathStore.h create mode 100644 pandatool/src/pfmprogs/CMakeLists.txt create mode 100644 pandatool/src/pfmprogs/config_pfmprogs.cxx create mode 100644 pandatool/src/pfmprogs/config_pfmprogs.h create mode 100644 pandatool/src/pfmprogs/pfmBba.cxx create mode 100644 pandatool/src/pfmprogs/pfmBba.h create mode 100644 pandatool/src/pfmprogs/pfmTrans.cxx create mode 100644 pandatool/src/pfmprogs/pfmTrans.h create mode 100644 pandatool/src/progbase/CMakeLists.txt create mode 100644 pandatool/src/progbase/p3progbase_composite1.cxx create mode 100644 pandatool/src/progbase/programBase.I create mode 100644 pandatool/src/progbase/programBase.cxx create mode 100644 pandatool/src/progbase/programBase.h create mode 100644 pandatool/src/progbase/test_prog.cxx create mode 100644 pandatool/src/progbase/withOutputFile.I create mode 100644 pandatool/src/progbase/withOutputFile.cxx create mode 100644 pandatool/src/progbase/withOutputFile.h create mode 100644 pandatool/src/progbase/wordWrapStream.cxx create mode 100644 pandatool/src/progbase/wordWrapStream.h create mode 100644 pandatool/src/progbase/wordWrapStreamBuf.I create mode 100644 pandatool/src/progbase/wordWrapStreamBuf.cxx create mode 100644 pandatool/src/progbase/wordWrapStreamBuf.h create mode 100644 pandatool/src/pstatserver/CMakeLists.txt create mode 100644 pandatool/src/pstatserver/p3pstatserver_composite1.cxx create mode 100644 pandatool/src/pstatserver/pStatClientData.cxx create mode 100644 pandatool/src/pstatserver/pStatClientData.h create mode 100644 pandatool/src/pstatserver/pStatFlameGraph.I create mode 100644 pandatool/src/pstatserver/pStatFlameGraph.cxx create mode 100644 pandatool/src/pstatserver/pStatFlameGraph.h create mode 100644 pandatool/src/pstatserver/pStatGraph.I create mode 100644 pandatool/src/pstatserver/pStatGraph.cxx create mode 100644 pandatool/src/pstatserver/pStatGraph.h create mode 100644 pandatool/src/pstatserver/pStatListener.cxx create mode 100644 pandatool/src/pstatserver/pStatListener.h create mode 100644 pandatool/src/pstatserver/pStatMonitor.I create mode 100644 pandatool/src/pstatserver/pStatMonitor.cxx create mode 100644 pandatool/src/pstatserver/pStatMonitor.h create mode 100644 pandatool/src/pstatserver/pStatPianoRoll.I create mode 100644 pandatool/src/pstatserver/pStatPianoRoll.cxx create mode 100644 pandatool/src/pstatserver/pStatPianoRoll.h create mode 100644 pandatool/src/pstatserver/pStatReader.cxx create mode 100644 pandatool/src/pstatserver/pStatReader.h create mode 100644 pandatool/src/pstatserver/pStatServer.cxx create mode 100644 pandatool/src/pstatserver/pStatServer.h create mode 100644 pandatool/src/pstatserver/pStatStripChart.I create mode 100644 pandatool/src/pstatserver/pStatStripChart.cxx create mode 100644 pandatool/src/pstatserver/pStatStripChart.h create mode 100644 pandatool/src/pstatserver/pStatThreadData.I create mode 100644 pandatool/src/pstatserver/pStatThreadData.cxx create mode 100644 pandatool/src/pstatserver/pStatThreadData.h create mode 100644 pandatool/src/pstatserver/pStatTimeline.I create mode 100644 pandatool/src/pstatserver/pStatTimeline.cxx create mode 100644 pandatool/src/pstatserver/pStatTimeline.h create mode 100644 pandatool/src/pstatserver/pStatView.I create mode 100644 pandatool/src/pstatserver/pStatView.cxx create mode 100644 pandatool/src/pstatserver/pStatView.h create mode 100644 pandatool/src/pstatserver/pStatViewLevel.I create mode 100644 pandatool/src/pstatserver/pStatViewLevel.cxx create mode 100644 pandatool/src/pstatserver/pStatViewLevel.h create mode 100644 pandatool/src/ptloader/CMakeLists.txt create mode 100644 pandatool/src/ptloader/config_ptloader.cxx create mode 100644 pandatool/src/ptloader/config_ptloader.h create mode 100644 pandatool/src/ptloader/loaderFileTypePandatool.cxx create mode 100644 pandatool/src/ptloader/loaderFileTypePandatool.h create mode 100644 pandatool/src/text-stats/CMakeLists.txt create mode 100644 pandatool/src/text-stats/textMonitor.I create mode 100644 pandatool/src/text-stats/textMonitor.cxx create mode 100644 pandatool/src/text-stats/textMonitor.h create mode 100644 pandatool/src/text-stats/textStats.cxx create mode 100644 pandatool/src/text-stats/textStats.h create mode 100644 pandatool/src/vrml/CMakeLists.txt create mode 100644 pandatool/src/vrml/parse_vrml.cxx create mode 100644 pandatool/src/vrml/parse_vrml.h create mode 100644 pandatool/src/vrml/standardNodes.wrl create mode 100644 pandatool/src/vrml/standardNodes.wrl.c create mode 100644 pandatool/src/vrml/standardNodes.wrl.pz.c create mode 100644 pandatool/src/vrml/standard_nodes.cxx create mode 100644 pandatool/src/vrml/standard_nodes.h create mode 100644 pandatool/src/vrml/vrmlLexer.cxx.prebuilt create mode 100644 pandatool/src/vrml/vrmlLexer.lxx create mode 100644 pandatool/src/vrml/vrmlLexerDefs.h create mode 100644 pandatool/src/vrml/vrmlNode.cxx create mode 100644 pandatool/src/vrml/vrmlNode.h create mode 100644 pandatool/src/vrml/vrmlNodeType.cxx create mode 100644 pandatool/src/vrml/vrmlNodeType.h create mode 100644 pandatool/src/vrml/vrmlParser.cxx.prebuilt create mode 100644 pandatool/src/vrml/vrmlParser.h.prebuilt create mode 100644 pandatool/src/vrml/vrmlParser.yxx create mode 100644 pandatool/src/vrml/vrmlParserDefs.h create mode 100644 pandatool/src/vrmlegg/CMakeLists.txt create mode 100644 pandatool/src/vrmlegg/indexedFaceSet.cxx create mode 100644 pandatool/src/vrmlegg/indexedFaceSet.h create mode 100644 pandatool/src/vrmlegg/vrmlAppearance.cxx create mode 100644 pandatool/src/vrmlegg/vrmlAppearance.h create mode 100644 pandatool/src/vrmlegg/vrmlToEggConverter.cxx create mode 100644 pandatool/src/vrmlegg/vrmlToEggConverter.h create mode 100644 pandatool/src/vrmlprogs/CMakeLists.txt create mode 100644 pandatool/src/vrmlprogs/vrmlToEgg.cxx create mode 100644 pandatool/src/vrmlprogs/vrmlToEgg.h create mode 100644 pandatool/src/vrmlprogs/vrmlTrans.cxx create mode 100644 pandatool/src/vrmlprogs/vrmlTrans.h create mode 100644 pandatool/src/win-stats/CMakeLists.txt create mode 100644 pandatool/src/win-stats/winStats.cxx create mode 100644 pandatool/src/win-stats/winStats.h create mode 100644 pandatool/src/win-stats/winStatsChartMenu.cxx create mode 100644 pandatool/src/win-stats/winStatsChartMenu.h create mode 100644 pandatool/src/win-stats/winStatsFlameGraph.cxx create mode 100644 pandatool/src/win-stats/winStatsFlameGraph.h create mode 100644 pandatool/src/win-stats/winStatsGraph.cxx create mode 100644 pandatool/src/win-stats/winStatsGraph.h create mode 100644 pandatool/src/win-stats/winStatsLabel.I create mode 100644 pandatool/src/win-stats/winStatsLabel.cxx create mode 100644 pandatool/src/win-stats/winStatsLabel.h create mode 100644 pandatool/src/win-stats/winStatsLabelStack.cxx create mode 100644 pandatool/src/win-stats/winStatsLabelStack.h create mode 100644 pandatool/src/win-stats/winStatsMenuId.h create mode 100644 pandatool/src/win-stats/winStatsMonitor.I create mode 100644 pandatool/src/win-stats/winStatsMonitor.cxx create mode 100644 pandatool/src/win-stats/winStatsMonitor.h create mode 100644 pandatool/src/win-stats/winStatsPianoRoll.cxx create mode 100644 pandatool/src/win-stats/winStatsPianoRoll.h create mode 100644 pandatool/src/win-stats/winStatsServer.cxx create mode 100644 pandatool/src/win-stats/winStatsServer.h create mode 100644 pandatool/src/win-stats/winStatsStripChart.cxx create mode 100644 pandatool/src/win-stats/winStatsStripChart.h create mode 100644 pandatool/src/win-stats/winStatsTimeline.cxx create mode 100644 pandatool/src/win-stats/winStatsTimeline.h create mode 100644 pandatool/src/win-stats/winstats_composite1.cxx create mode 100644 pandatool/src/xfile/CMakeLists.txt create mode 100644 pandatool/src/xfile/config_xfile.cxx create mode 100644 pandatool/src/xfile/config_xfile.h create mode 100644 pandatool/src/xfile/p3xfile_composite1.cxx create mode 100644 pandatool/src/xfile/standardTemplates.x create mode 100644 pandatool/src/xfile/standardTemplates.x.c create mode 100644 pandatool/src/xfile/standardTemplates.x.pz.c create mode 100644 pandatool/src/xfile/standard_templates.cxx create mode 100644 pandatool/src/xfile/standard_templates.h create mode 100644 pandatool/src/xfile/windowsGuid.I create mode 100644 pandatool/src/xfile/windowsGuid.cxx create mode 100644 pandatool/src/xfile/windowsGuid.h create mode 100644 pandatool/src/xfile/xFile.I create mode 100644 pandatool/src/xfile/xFile.cxx create mode 100644 pandatool/src/xfile/xFile.h create mode 100644 pandatool/src/xfile/xFileArrayDef.I create mode 100644 pandatool/src/xfile/xFileArrayDef.cxx create mode 100644 pandatool/src/xfile/xFileArrayDef.h create mode 100644 pandatool/src/xfile/xFileDataDef.I create mode 100644 pandatool/src/xfile/xFileDataDef.cxx create mode 100644 pandatool/src/xfile/xFileDataDef.h create mode 100644 pandatool/src/xfile/xFileDataNode.I create mode 100644 pandatool/src/xfile/xFileDataNode.cxx create mode 100644 pandatool/src/xfile/xFileDataNode.h create mode 100644 pandatool/src/xfile/xFileDataNodeReference.I create mode 100644 pandatool/src/xfile/xFileDataNodeReference.cxx create mode 100644 pandatool/src/xfile/xFileDataNodeReference.h create mode 100644 pandatool/src/xfile/xFileDataNodeTemplate.I create mode 100644 pandatool/src/xfile/xFileDataNodeTemplate.cxx create mode 100644 pandatool/src/xfile/xFileDataNodeTemplate.h create mode 100644 pandatool/src/xfile/xFileDataObject.I create mode 100644 pandatool/src/xfile/xFileDataObject.cxx create mode 100644 pandatool/src/xfile/xFileDataObject.h create mode 100644 pandatool/src/xfile/xFileDataObjectArray.I create mode 100644 pandatool/src/xfile/xFileDataObjectArray.cxx create mode 100644 pandatool/src/xfile/xFileDataObjectArray.h create mode 100644 pandatool/src/xfile/xFileDataObjectDouble.I create mode 100644 pandatool/src/xfile/xFileDataObjectDouble.cxx create mode 100644 pandatool/src/xfile/xFileDataObjectDouble.h create mode 100644 pandatool/src/xfile/xFileDataObjectInteger.I create mode 100644 pandatool/src/xfile/xFileDataObjectInteger.cxx create mode 100644 pandatool/src/xfile/xFileDataObjectInteger.h create mode 100644 pandatool/src/xfile/xFileDataObjectString.I create mode 100644 pandatool/src/xfile/xFileDataObjectString.cxx create mode 100644 pandatool/src/xfile/xFileDataObjectString.h create mode 100644 pandatool/src/xfile/xFileNode.I create mode 100644 pandatool/src/xfile/xFileNode.cxx create mode 100644 pandatool/src/xfile/xFileNode.h create mode 100644 pandatool/src/xfile/xFileParseData.I create mode 100644 pandatool/src/xfile/xFileParseData.cxx create mode 100644 pandatool/src/xfile/xFileParseData.h create mode 100644 pandatool/src/xfile/xFileTemplate.I create mode 100644 pandatool/src/xfile/xFileTemplate.cxx create mode 100644 pandatool/src/xfile/xFileTemplate.h create mode 100644 pandatool/src/xfile/xLexer.cxx.prebuilt create mode 100644 pandatool/src/xfile/xLexer.lxx create mode 100644 pandatool/src/xfile/xLexerDefs.h create mode 100644 pandatool/src/xfile/xParser.cxx.prebuilt create mode 100644 pandatool/src/xfile/xParser.h.prebuilt create mode 100644 pandatool/src/xfile/xParser.yxx create mode 100644 pandatool/src/xfile/xParserDefs.h create mode 100644 pandatool/src/xfileegg/CMakeLists.txt create mode 100644 pandatool/src/xfileegg/p3xfileegg_composite1.cxx create mode 100644 pandatool/src/xfileegg/xFileAnimationSet.I create mode 100644 pandatool/src/xfileegg/xFileAnimationSet.cxx create mode 100644 pandatool/src/xfileegg/xFileAnimationSet.h create mode 100644 pandatool/src/xfileegg/xFileFace.cxx create mode 100644 pandatool/src/xfileegg/xFileFace.h create mode 100644 pandatool/src/xfileegg/xFileMaker.cxx create mode 100644 pandatool/src/xfileegg/xFileMaker.h create mode 100644 pandatool/src/xfileegg/xFileMaterial.cxx create mode 100644 pandatool/src/xfileegg/xFileMaterial.h create mode 100644 pandatool/src/xfileegg/xFileMesh.cxx create mode 100644 pandatool/src/xfileegg/xFileMesh.h create mode 100644 pandatool/src/xfileegg/xFileNormal.cxx create mode 100644 pandatool/src/xfileegg/xFileNormal.h create mode 100644 pandatool/src/xfileegg/xFileToEggConverter.cxx create mode 100644 pandatool/src/xfileegg/xFileToEggConverter.h create mode 100644 pandatool/src/xfileegg/xFileVertex.cxx create mode 100644 pandatool/src/xfileegg/xFileVertex.h create mode 100644 pandatool/src/xfileegg/xFileVertexPool.h create mode 100644 pandatool/src/xfileprogs/CMakeLists.txt create mode 100644 pandatool/src/xfileprogs/eggToX.cxx create mode 100644 pandatool/src/xfileprogs/eggToX.h create mode 100644 pandatool/src/xfileprogs/xFileToEgg.cxx create mode 100644 pandatool/src/xfileprogs/xFileToEgg.h create mode 100644 pandatool/src/xfileprogs/xFileTrans.cxx create mode 100644 pandatool/src/xfileprogs/xFileTrans.h create mode 100644 scene/__pycache__/util.cpython-312.pyc create mode 100644 scene/util.py diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml index 4dfb51a6..a67ef38e 100644 --- a/.idea/AugmentWebviewStateStore.xml +++ b/.idea/AugmentWebviewStateStore.xml @@ -3,7 +3,7 @@ diff --git a/QPanda3D/Panda3DWorld.py b/QPanda3D/Panda3DWorld.py index d26cdb5b..1dec236a 100644 --- a/QPanda3D/Panda3DWorld.py +++ b/QPanda3D/Panda3DWorld.py @@ -8,6 +8,8 @@ Description : import sys import os +from core.CustomMouseController import CustomMouseController + # 获取 RenderPipelineFile 的路径 render_pipeline_path = './RenderPipelineFile' # 将该路径添加到 sys.path 中,确保 Python 能够找到它 @@ -39,7 +41,7 @@ import platform # Local imports from QPanda3D.QMouseWatcherNode import QMouseWatcherNode -from rpcore.render_target import RenderTarget +from RenderPipelineFile.rpcore.render_target import RenderTarget __all__ = ["Panda3DWorld"] _global_world_instance=None @@ -51,7 +53,7 @@ class Panda3DWorld(ShowBase): Panda3DWorld : A class to handle all panda3D world manipulation """ - def __init__(self, width=1240, height=720, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1), + def __init__(self, width=1254, height=729, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1), name="qpanda3D"): global _global_world_instance @@ -61,39 +63,34 @@ class Panda3DWorld(ShowBase): sort = -100 self.parent = None - # self.width = width - # self.height = height - - loadPrcFileData("", "win-size {} {}".format(width, height)) + # 设置基本配置 loadPrcFileData("", "show-frame-rate-meter 0") + loadPrcFileData("", "window-type offscreen") # 设置为离屏渲染 loadPrcFileData("", f"win-size {width} {height}") - #loadPrcFileData("", "sync-video #t") - #loadPrcFileData("", "force-flush #t") + loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小 if (is_fullscreen): loadPrcFileData("", "fullscreen #t") - else: - loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer - #loadPrcFileData("", "show-frame-rate-meter 0") # 可选 + self.render_pipeline = RenderPipeline() self.render_pipeline.pre_showbase_init() ShowBase.__init__(self) + + # 初始化渲染管线并设置可调整大小的标志 self.render_pipeline.create(self) _global_render_pipeline = self.render_pipeline - #from RenderPipelineFile.toolkit.material_editor.main import MaterialEditor - #self.screenTexture = self.render_pipeline._final_stage.target.color_tex - #self.screenTexture.setComponentType(Texture.T_unsigned_byte) - #self.screenTexture.setFormat(Texture.F_rgba8) # 创建 Qt 能读的 RGBA8 贴图 self.qt_output_tex = Texture("qt_output_tex") self.qt_output_tex.set_format(Texture.F_rgba8) self.qt_output_tex.set_component_type(Texture.T_unsigned_byte) - #self.screenTexture.set_keep_ram_image(True) - # 添加到主窗口渲染输出,并指定复制回 CPU + # # 获取图形管线对象 + # gsg = self.win.getGsg() + # host = gsg.getEngine().getHost() + self.win.add_render_texture(self.qt_output_tex, GraphicsOutput.RTM_copy_ram) #self.screenTexture = Texture() @@ -137,21 +134,23 @@ class Panda3DWorld(ShowBase): #render_pipeline.set_camera(self.cam) - #添加渲染效果后 + #添加渲染效果�� self.cam = self.render_pipeline._showbase.cam self.camNode = self.cam.node() self.camLens = self.camNode.get_lens() self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam #self.render_pipeline.daytime_mgr.update() - # + # if clear_color is None: # self.buff.set_clear_active(GraphicsOutput.RTPColor, False) # else: # self.buff.set_clear_color(clear_color) # self.buff.set_clear_active(GraphicsOutput.RTPColor, True) - self.disableMouse() + # self.disableMouse() + self.mouse_controller = CustomMouseController(self) + self.mouse_controller.setUp() def render_pipeline(self): """获取 RenderPipeline 实例""" @@ -175,6 +174,20 @@ def get_render_pipeline(): "RenderPipeline has not been initialized yet. Please create a Panda3DWorld instance first.") return _global_render_pipeline +def resize_buffer(self, width: int, height: int): + """根据新窗口尺寸调整 Panda3D 渲染输出尺寸""" + # 设置 Panda3D 的窗口尺寸(offscreen 模式下对应输出区域) + props = WindowProperties() + props.set_size(width, height) + self.win.request_properties(props) + # 重新分配输出贴图的大小 + self.qt_output_tex.set_x_size(width) + self.qt_output_tex.set_y_size(height) + # 更新 lens 的 aspect ratio + if self.camLens: + self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height) + # 强制更新窗口(有时在 Qt 内嵌时需要) + self.graphicsEngine.open_windows() diff --git a/QPanda3D/QPanda3DWidget.py b/QPanda3D/QPanda3DWidget.py index f2e5a921..25a6158c 100644 --- a/QPanda3D/QPanda3DWidget.py +++ b/QPanda3D/QPanda3DWidget.py @@ -158,23 +158,19 @@ class QPanda3DWidget(QWidget): print("Unimplemented key. Please send an issue on github to fix this problem") def resizeEvent(self, evt): - # new_width = evt.size().width() - # new_height = evt.size().height() - # - # # 更新 Panda3D 相机的 Film Size,保持视野正常 - # lens = self.panda3DWorld.cam.node().get_lens() - # lens.set_film_size( - # self.initial_film_size.width() * new_width / self.initial_size.width(), - # self.initial_film_size.height() * new_height / self.initial_size.height() - # ) - # - # if hasattr(self.panda3DWorld, "win"): - # winprops = WindowProperties() - # winprops.set_size(new_width, new_height) - # self.panda3DWorld.win.request_properties(winprops) + width = evt.size().width() + height = evt.size().height() + print(f"width:{width}") + print(f"height:{height}") + + from Panda3DWorld import resize_buffer + #resize_buffer(width, height) + lens = self.panda3DWorld.cam.node().get_lens() - lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(), - self.initial_film_size.height() * evt.size().height() / self.initial_size.height()) + # lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(), + # self.initial_film_size.height() * evt.size().height() / self.initial_size.height()) + #self.sync_panda3d_window_size(width, height) + #self.panda3DWorld.buff.setSize(evt.size().width(), evt.size().height()) def minimumSizeHint(self): @@ -225,33 +221,112 @@ class QPanda3DWidget(QWidget): # self.rp_sync_requested = False # self.update() + # def paintEvent(self, event): + # tex = self.panda3DWorld.qt_output_tex + # + # gsg = base.win.getGsg() + # + # if not tex.hasRamImage(): + # base.graphicsEngine.extractTextureData(tex, gsg) + # self.update() # 请求下一帧更新 + # return + # + # data = tex.getRamImage().getData() + # width = tex.getXSize() + # height = tex.getYSize() + # expected_len = width * height * 4 + # + # if len(data) != expected_len: + # print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制") + # self.update() + # return + # + # # 一切正常才绘制 + # img = QImage(data, width, height, QImage.Format_ARGB32).mirrored() + # + # painter = QPainter(self) + # painter.drawImage(0, 0, img) + # painter.end() + def paintEvent(self, event): tex = self.panda3DWorld.qt_output_tex - gsg = base.win.getGsg() + if not gsg: + self.update() + return + if not tex.hasRamImage(): base.graphicsEngine.extractTextureData(tex, gsg) - self.update() # 请求下一帧更新 + self.update() return data = tex.getRamImage().getData() - width = tex.getXSize() - height = tex.getYSize() - expected_len = width * height * 4 + tex_width = tex.getXSize() + tex_height = tex.getYSize() + expected_len = tex_width * tex_height * 4 if len(data) != expected_len: print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制") self.update() return - # 一切正常才绘制 - img = QImage(data, width, height, QImage.Format_ARGB32).mirrored() + img = QImage(data, tex_width, tex_height, QImage.Format_ARGB32).mirrored() + + widget_width = self.width() + widget_height = self.height() painter = QPainter(self) - painter.drawImage(0, 0, img) + + # 【保持宽高比的缩放】 + scaled_img = img.scaled(widget_width, widget_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) + + # 居中绘制 + x_offset = (widget_width - scaled_img.width()) // 2 + y_offset = (widget_height - scaled_img.height()) // 2 + + painter.drawImage(x_offset, y_offset, scaled_img) painter.end() + def sync_panda3d_window_size(self, width, height): + """同步 Panda3D 窗口尺寸到 Qt 窗口尺寸""" + try: + from panda3d.core import WindowProperties, LVecBase2i + + # 确保尺寸是4的倍数(RenderPipeline 要求) + adjusted_width = width - width % 4 + adjusted_height = height - height % 4 + + # 更新窗口属性 + props = WindowProperties() + props.setSize(adjusted_width, adjusted_height) + + # 对于 offscreen 渲染,直接更新窗口属性 + if self.panda3DWorld.win: + self.panda3DWorld.win.request_properties(props) + + # 手动触发 RenderPipeline 的尺寸更新逻辑 + if hasattr(self.panda3DWorld, 'render_pipeline'): + # 更新全局分辨率 + from RenderPipelineFile.rpcore.globals import Globals + Globals.native_resolution = LVecBase2i(adjusted_width, adjusted_height) + + # 重新计算渲染分辨率 + self.panda3DWorld.render_pipeline._compute_render_resolution() + + # 通知各个管理器处理尺寸变化 + self.panda3DWorld.render_pipeline.light_mgr.compute_tile_size() + self.panda3DWorld.render_pipeline.stage_mgr.handle_window_resize() + if hasattr(self.panda3DWorld.render_pipeline, 'debugger'): + self.panda3DWorld.render_pipeline.debugger.handle_window_resize() + + # 触发插件的窗口尺寸变化钩子 + self.panda3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized") + + print(f"Panda3D 窗口尺寸已同步为: {adjusted_width} x {adjusted_height}") + + except Exception as e: + print(f"同步 Panda3D 窗口尺寸失败: {str(e)}") diff --git a/QPanda3D/__pycache__/Panda3DWorld.cpython-312.pyc b/QPanda3D/__pycache__/Panda3DWorld.cpython-312.pyc index 9cb532446d9664c1fa5e8fcc71aedeee6fe761c6..10ce1618f7dcf24c5e0feb7b3330b71fcc736d04 100644 GIT binary patch delta 2542 zcmZt{OKcm*b!ItBEy>-bNSUNW{g(RBVggH6T`5lN8vZm+fW)$6ATY8yY-#P%CcFH2 zc8OR53(2huxk!q{7&tM|6jqTO0vk!yv_L+G9D3-bC~Cqqq;6cmJxGmvl4Aiad~s)% zTG9?Oz|Q->H}m$rc@KAfIwAj6l7fJ)&whL5Y%O_N?jHXO1mHcO0~P2{=S-+V7jh~` zq0jUck%}PDk;)rfK_supU5POB@Z|DjPe-2QGs+0upfT9;pc*t_)7NOCDS3R?p%J{n zH8~J?nhW2r$+tD>d3jSTN~%Q9%VlLs`_1sj9Hki6*}@_%EV_+#m|evk>JC>^Fr&qo z8Y{-tcrl?Sirs2A<1gp|v!|F;ldckH_Nu+qZP4s1_N)CaFJulB2h~A}i)Wy!&?BU% z24@bbLmYSk=+Z?hF_JfY&8w-yx_l9+BYM~v)jN&R9MU5fk(!?4GSPV}c&w~fO+$H3 zIgu^t**!ld_u$!{40K{gs+L_Y9xGQZ<4Cz=Zq}&q64ZH+{$b8wh~>KgeC* zK{!HEC_@V9D0vG>az{H?H6JE_L&?DoFY!0Fks8pbd2`jks{nThaf{>OP-)0 zlX*UM`P>c|X(CKucR;zNd!US{}yYdUzY!BOYEzGasQ=!MV4FW6T}-qN7WqcWB+LM(+FLa2GX5 zc2a(;Bx#IsW&kIdNT#CJCBNg2bLm54Ui3E9k{)h9<6*T{%TSknZEj(&8*QH(4T3q4 z3C`r-fAr{)1IP&>c`EiszNDAm*k#XE45ggQS+m$MO7j8A-eu+I4do$J<|B;C<> zlQ*23mvU9pY$_|mwj-UhwQ|+2RBg>R&SwV5C&DC$5xFOf!4QcCCj0_D6ONG=0;gaH zxfXb70JG|LI*Y74wb@pEI=p4ruU5$a0>AU`r5e^1axIul_(7oiDtEJQsv%6@5fTjD z(-3}0{uxYkZ_^VEVfVkpo;zai)x*o-hPa!^p{MyxYAKQZp#kV8uZIpok-Q&TN^QkK zYI;S#rR=+e8bbe?Ag&7ib)kP%7_JM$w}nwMDO%zLFp@W@SJ1he0O6J$$#)zAJk7Rhm`JIc&4qb*i43Ue|ijir_uhZWi)Sc~^vLT`oc zD3dGcTgtPS&~2d~KSTa3{pYoPR3YPc%^f~xma{gVpe($bZZt>Lih&*ZjA0+LD#omR zGHd6{86S2-4l$L0rp=mJ%hEK9C0bDy@ienPMEI}*KP2h!Ykv3G`^YcDC#Qbo%9x?F zZ8&R~rlw^eK1{i877jK&u=``S?IB|44~DXkPk(#V$MK_^L5_cJIq?8c@LvJ(6ZZk# z=zV_Dd9N93njDYx@W-g_byAFE4!Z|vU2Asxy`y3P5d9ea;Iun-?CrroM+1}6^)p}1 zSMZUS(SRQU+E$1bdRngeywSm!%~wgT!6%txV>Rw^PJ!L#I}7(wpR(ILk#>-%%ZRaV zTA3-m9!^eE#XiHS*0@ zWPRz&rov1ngxQRB1X}VM>o)BEx+pfzRSnD5D!5F4vz<3A%pMje$n?&;noe+m5pzwm zr{{#0AYA$DBe2NPHKtF4VZVQWG%MYafTjIdt^J^%uG;?9*ovEwo21>0VVd=#SFRr4& zIvQN&8z@b?kPob)cpb%8QL2to%cBi6M)jWHQQHjF6vU<4&ug?N-xhyG`*Nr*3@uN6 zlx_%<+intdlvq80M(SwfHcGqQm)VJr(nC2uYtC}GvgIUa%h(wI-Vx}&Jmh+$01=5r zvoSw4#B9D{_mK)PEJ)sr4$!CTdi1FUkvZOZ(kST$K9R2&CQawjylIT%iW>yG63xzH z+K-scKBq&|vaEeD>vKmqUCo=I5LgHM$xPRU0|(baJvW2N#g4m)-kTu)H3)C{s}N4S evv&(H_-)C@!{JT26YjifZ2}5DI$QjLfL4%xVnY=OjGn z4l~E_etS5oc{WKDeH<(E8l*}XzFC(9ev>((A$Y;P%C_93hD-Mue%TL!$`k@*@79H% z7_y^CY@%Qjg^m%Yw;AnnyRG!-AtNk@jffmEBv~?|a@6+odd!H+aT_ywhulH3de7?# zBPl0sr%z8AX*o@>|1y+2Nef*>!sr<}!+>{y8n{jvExySf%^`QI!gV0`EHPOTKZRF+ z`I;GrP{8-t54ar{0Hr#XTobSzf9%RKWe?u?773R7C?6n0s~(4j zpn{*c65U>h+&l&=Kxqc7fU*zYYwyEb)I;fn&)1ENC#s!8?itTcAE&D=j#)*}BTD*hG zFOz8`>4dCSABKENY#ph6UVnP}nDPKUfgYvV9$^NYge_anlfvolW@@4uYOxL~4^x$p zaQ6SNB0(P}B}JuqJcJ)75edA+2PegaQYB}WmbJ{)&&h8&X3kX?aTm{}HPYr1Y0k)~BLy%t{QV{HxLGfSz~tofRySlVJXjSu+Kz6Lj2MMl9w zEbjN7BE5X%&2t{oDvID6-b;|fyWR_8BcPVd!i=uHSFM>Ee%E)08zma^TfFZ}T=W5N ze2uB61|Ft{w^GA*Cx0EipE|eAzx|kxQUAy`KWf9Z(uR9|VWV)D+gPlp#XmZcRb*e z_(s4Cykkum>2rsZJZZkc8e&Ar-X|`^dga;ERB#sN4Qk5igOxzzp83k2GMv# zB_{m_WoVWy;S{`s4}~r`iWB0$9cKuUMzkNutOT*%bKMit)gm3nJ>+2da0tJDMD}J-`lbgD$Mqx zNn#qTxnwq&s)_Sb8M646RFLTR0&#S)FP^8!i$l>)@;chzW+Qlcc&vE|6FACaGOW4W>|nX(camW>d!?!-s=>fO(7#!~ezY%?N=ey1z#iSrRk8#)-&SGN*79S&6 z7vQOLR@V7CHbzey0`yNlqJHTC*R7PnkAoYYcJTYUR&cMb4JW~ay5<2wG$WsAu()Kn zj(yv38`pUp+puPkh^NjiS_-jNKXzl>Gr znt98d1y~i@E7RT;y1htN+{}E#TmxuQ-0iZveZ}oBn(kX1B~vL|iYz-@%a%68ny;Bk zQ_D_o+2UKZ^gJ>FSDTNTG4flW<8d66tPGIbviKr6;q5itCBU~@dcaNZ5i=pUB`j_O znc5~3j!k2d0X)B@#VGWOMsU>D0`td!{XRE%4&{=xf(hTB*6H?hMrhoL?v11%)s3iv zRSdvQwIN~EyUW`oOZ>U2eElm?l2zPZ<>Nh7YW=%m_4##amAY-zsk|L}vd23y4DhuL zo*XPpmdwG+%{wG3Tj;|vrWH#w zNOp9Sd^=PDZ+7&8-*k)?u0lq?;Lk%!H3{S41jq9Ih;g0bxJfnD_lxc36YypiHHzUjciy9s}$rz zdUaYwt}qqSOH{RUQ)nZO4Z#{yiC8kC+M!y66W6pIYuuW?ITMbhM2=Uj`jJD(!ZH;~ zPfQ4$s9L7iD8U6ofz`Tk@GU4jEA#-q=5Ign8RUek@U6l^c6t6wkrL}$}8=@n;Nk@%y_Q?b*tr)EyQF;ua8zKfRrPjC5r*!O=J9e*h?2(y0OFp^gK+#fRd<%t>ihqyn-?QT1Co}t&`sJEK zMau(=OR+S{md0ya7rt;aUbehYvAAY!Gq$ff^cT2RPcTCmhWZ=gKfML#d5nU6jx{8( zB%UXd2CaMH63!Ai*dI{6=1IvQ8J_SpSl(p7B;q!51xMa2nL&nRoDwOSZleN)1INBz zTL<3X!Wj5YovH;tCzx7u^*O>;iY`1?teTc^-VF~#CM-!RZ^1FRmvN(O$r93< zJ~D-tb@4jg%f>n0h@Ol=JH@t56<|MncEq>*Kcm)XO)xC(Lq_&z!trjlC;KEjd=EVM z=I26>`kA3`p$Ajg%yHcRj0I%l-Tx_LH)j7yi)1Ylc^zXZ$pqodxPCv=whm0(R`ue? zcwe3p3+rZ95kZSKB&urmG$LNWSX-WYm72Ec-${HDn<6e?lX$?MMV)rF^v0im@V0ji z(7b=SHNQIh*6QMWt3O&=EiQUT#HU0MDhm+U3_L1e)q46IH-0QEaJyps>qy|@Vb|jL zP$rh)60sBqLDhUBk-i*G2&#qUE@vYV*k+uQu@sx04C!#oW>La7-T$XQ)=&3v>AgO0 zj_^)-vmI#v|8?f?zWdiZZ})kJ1NEvsqoFk0GoI!-?Zo`|&?(KZtBpdS*D`{| zQkE0MP=-%uI9`l#f@+S2g|H~{s_(28RZtJAzzxI_To0c?UV9>XbUb>BsyhYS_!SNX zmQG|*N9S)Lc~fXSoy=rKw4&u_T|uQ1=pQ7MjPRhb$n`^%V-Eg2qOBk(f&);0Sn!xo*QSF zjb{af9XhbMp73j6K^9Po1U+D!6Hp^q2Lv4s8ieW8cxWyA(2;iAD4tyR$l67p*YK$4 zzbSS=2ob)b;m9MSpE`#B!QKyha%;!J%Y~aGH!jFqdWyr9R{z44Mf^riZtX3e`2@D? z&K0Jsc)VhF&koHDUF*5Ivuy8!W;CO?yt2!yxct9$`74m6eKPG+=uVmLROtOOy??2; zOdt6>RIM+GO8r*3e(UnK10TB%uF}VUw|(#8tM>{k+s~KGWjauR4&Aj?54|$otI(%q z`gEBd-el!~Odn9_A(9hB1AU-M0+vVo%O7lUv`QTE#Tr*f< z8Wg5eW;zuHp^(B94$I78g&CEZ(K7S$CR-kq>4OSADAR+>C&$Y4#fNtHG6O?c-aBw_ z{MS2|2gjB@7eBUN`sYJe!($AG$-R#>_T43x`hR9xoLcrARqXw;z5laE1X}%<&;alF zwSmrot=I=!siqSi;)CA$6At264ieG7fK6rS1j#_QM~JGM#WtmBQr2`nWER$Bs9!cT+Rlm^SLOQDgfV6xk+9UMnv z3raAQ_T(I@B)mf`6;*J8DjcZF0fYoXRZm=)F49_745brSWoA15v z>CJm{WAz%$Ze9q)~?(ayZvXW1&(JIfO6bySy75TtI)uQ7{6B z1M#oxuBPm> zH*e)iOmUZWSO?t=UM|2b!Z z?ynB;*R-vMHt=(F&QtRgx4xiXbGoEUHF?7s1UHO$mc~oPVR_4xn>u6HHo@;(wt}2Y zNpv&6!=7ZWdxC?ON2mGIAC>ZRMoTmnbiR|v$=F-;ERWHeli)MHS~O;>hCaim-4Y9> zxt4n|Us$LbwBme1I5n_q$5Rd}0&nvIYLbvmR7O;r3^6iP7oU>Qin(mAl$XmEQR^c7 zG4OsO7+&|S`GC}=W>;iAwibhUy`$L^Yn)m;e+{!94L%+mJ@QWn3T#^awDIoc!Rv$e z_PuQTUW@Fr$s{9_7CFRghxlq?xbAMs0b7nTIoe1xvX{k2a*{JE-evE1e%i;I)5J7Q za{qxAgo%-~_dLh+8|U~Qu8bsb27Vbi810hE_^)kPQvW`Vqf&QUnbxB85)AI#Pg=gk p{DPi;TOXb+Qv>FAHaoBK3eIvWpTpr)rt5%+yH}4sLym8Y{|8fe5v2eC diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index c56e81ec..864c1be0 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,8 +17,8 @@ control_points: scattering: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] - sun_azimuth: [[[0.5000000000,0.6138888889]]] - sun_altitude: [[[0.5000000000,0.7111111111]]] + sun_azimuth: [[[0.5000000000,0.1472222222]]] + sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/RenderPipelineFile/samples/06-Car/main.py b/RenderPipelineFile/samples/06-Car/main.py index dcee5499..da6715f8 100644 --- a/RenderPipelineFile/samples/06-Car/main.py +++ b/RenderPipelineFile/samples/06-Car/main.py @@ -54,7 +54,7 @@ class MainApp(ShowBase): # model = loader.loadModel("scene2/Scene.bam") model_0 = self.loader.loadModel("/home/tiger/下载/Benci/source/s65/s65/s65.fbx") model_0.reparentTo(self.render) - model_0.setScale(0.25) + model_0.setScale(0.01) model_0.setPos(-8, 42, 0) model_0.setHpr(0, 90, 0) diff --git a/__pycache__/gui_preview_window.cpython-312.pyc b/__pycache__/gui_preview_window.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9b3c6c26caa4059bd48951cb127d9c700a0dac4 GIT binary patch literal 23679 zcmeHvYgkify6DOc5(s1g;Ud=%u8}*aD2fzBt%`_>qf#9jSrHNu=t@!x6I$EpOtG~U zJMBnYyGt*#o7T26yY3m<+LpO=hV$(Cu@;H5;u`n!IC~qAANv_bJLmN2?DL%Se&5PU zRzjqmJ^TDS9}nO9uJ3nyzsq~eza%8Y5#SEp9qwB?LJ0| zTCCHhwrD8LiBf`~lc1G2D(;d@gofksq4=lfUxWO(?FV)&zVYPkx5pOVz2HCh zrlG=c>(kE{UT`lwH#L9p%>KT?Q!Qq@#Xt6n|C5gw-+0yk){ljPf4X}1*7T1SFSw!X z{Hs5ipZILyt&ahi{|Db+xH1`c`|UUVKm2g7WAT+A&X0}Xnts2w)d0CdvBe7?EWR-T z5EdW*-2a1T{cpW|Yx><_Qy=`ptWNo z@Ru_1tNI2{TB;n@UJG3{|Kq3TUw^Y|>-H+t6R{nkua!f?yt=#FI$(8lcaN-!T;WPF zD+OTMcY$}B@Z}oElwNg~FWZ2=t9;qH!Z#DXXLUZ|vlK(*7oqeVpP?}9drO_bB-UP2 z*WCqhL{D%*A%emf84f|<0^yQLk-;US^Ie23xa3eI;4THeGC&YpaM{INf(Ydg%Z6np zImX-DAAGj(=?Ax_KUr`;^UJ?J*7h#Y4Kl)j4b)^g6Zc@+*0F_U{(-l6EJZX2B zX-9WxNlkKEk4mdBXgeC4!9b^?q(r;L(H%mBk-TN|9$p|HD%nloohEK(=Al`>9#8tT zB&RK8wS``7F%IXm+Wc$Uf+ZE9O`_AGbl65ihd7?wSs23wLWAZCk|Ld5>j=(p*p`q- zFFz`y6r*xVIjYbQl!}V!m!DLODq$s*Hcce#&`OM|Tq=6prLx7}yG$!_^dW*6jd7qk z$IcZ)#fq1JU3Qjq#n^OWUg#^X_Gqjt7D^RJ>c@sI5y+`_#oB_~KT3TyQs4rG6FG=Ya3VB>N{FGb-e-mNK6J8VI~v9E(V>X-Y@5Y$p)c$q$-LiV ztj(eFVo01H86i0nqK<*e5m%o6&*=SIanB^(qs~hEC|QC(xvnI9S|o>`7B1LBC+gw6 zQMr;`3KXK>m(UYaI@i7DLinl9es_wx>+^i6Vhe z`i&=rNjxbcV1!;RQTGlNtVesIucPe&ade@txK9H2?9Tu%fFzG}wh#ps%yO8;VD#4D z?Y8<-;CvQWU^Ly89u-!P2#YEc%ZlI8jOZ8?6^ zhQ#@?oe)z(UsuLTHNcARFdSxj6|H)`ihI%cA#F610I4GuuzM=eY z#Msc?wmmv7ta!Kmy{ByzK5f%naN1JUuS(8{;abTI*GTTNh*AZF1JPC36%w>AY{e2n zD|46_SDn4!NRyN;7>$#(k?2?@=DC6_BP|G6Lgr#|*yo^CLVC=8DnoapH~fAQV< zGoSg#ruI4v9p(YbT-Pd8Ks+|gj9dI<;`Rq`2{kdDE~(=G_yxqS!}Piz^Uk7IE0DI- zF}&BYHhQ^Wr={KXzxC7GSANo9a3V@QYQ1)6t}xob2m~&n6b96;h9Y74!jR@KOwPYJ z78NX1DuhZA+$P{s3j9Q1K_dPmWxdpjsTc*< z&>Dnw3$s;$LF#|{H5fi(qW!}&4F+BfD0FDQb;ROCHDFa2U;2Rn4l@x$*efEF6Fq8f zzyFW^^G`PzgsB&@ZcRVyKl{<*51#$g)yL;AzBYgH6_`4E zKE#y-9^~@HM{{;{a(?nD|MO=TU-)3*m5JM*{mB1=pE%J=_TOHfG~E9Ahqpg}N+?qm zD1jMaf4UlAi^DX|zi@f}?BnxeKNb!N0R%MWf9t6~T|MJO_i_}zD0E0zUSatF;QYnY z3!jXMv+IOY1vb>*R3ELPSijSW2u}fc1WJl~*3^I*baXNlIji(BW?$l`!I^~q<{h)^x%+%c35HAV}ZMev(ymi zv^x4NyaqyhXlpOPLCnDezyt0Xq(J~c@ya3VkcCg(MgtqT#d-X=g_eMdgJxPS$IZ@u z2cOs*z`3mh0LOw@%_l5-oDM)ZUMu2b2Lh&R*|yK}kkd+Aguc;eoA64#jeFtf)!-8jHH_QWVHTUm1ew>eo z?9Nkz`>m9PqS1NDt0}X?yl-&O!6(4#nS1)$EvHb!Jc+ke^8mHWGJI^%OjEoTr-)li z)v>*EaEL}MKwde3YUluXSP>2Mm=f6di9NIh`q2$6bnC$JL0$t_V2|Y(44KzpE_}D; zksec~!1Gle14)v|>&JK$kJ-HfG6|TdFb@s!F@R;TgnVM)P6ZJHj;kMXpdcWi20{TG zlv#KI44@l`U-zf806{ET$DAfl^vQ=Fkl|X&vO{Cr`ltDf&CmgdH8KhI=~9@utyLJ5soe%^ii2 zj*fTxqrh!CO)M$9<(gH#l=KTt&o|v5Q<<~|FS*f|o(;kUea3~R^G#!|5X{UO+vx*u z*HTPI!d}_GlIwkGMlP+4O)K-HRg5dHr=;C97IDV4tZ}W!Sbv8|NbQhK#N5a-POft| zUCY|&tEl5Dn%RnGPsNsrc0RA-O38HPY{|9SeK*TCaAi$wS(B%%c~b2wZ{W(C+45#j z`IbqIub|9resj~*jv3j9yL~kqCU^R3H%{)lQD|byx6asRjxc*WnF9|pon~hBF>hhd z^+MB9I%@jMOk!0oliPkRW6v%9DkihttKYMfNRXKm`{#6q$p@yk%oWvxl;yR)m-@!z zuczcrZsZEqu?6eA1?zpr3eLEWHLmj**N^Y=6;yHs^=v`Cr(nZ)n=josx%c_U+?%HJ z=gKzvimE5#e8sgB@i#JZ7{li2k?H=~g4q+ZN15CQu4NoSO=q{wcFndihaO;#^fC|h zF_~7c-u7h-k!-n z(W|Y16i%DNYI7zmUTq-&Nl0e&Yo}~eM`n^{HqKPcjxb#m(`#kBtju8>V;f|WhrHT{ zgr(eYe#2Pn^^|nH$m_l223TYOky6Sf*RsjAp5!%STcBh?iLi>C@jljgpO4ftX(jF? zADJ?~?_xH$YAw5J?Om0kAaShKtIb=A10>;6isw>_rC@JqlNf#dRpoRi*Vw@}c6iqB z<<@tx>${lx2bkm|UhPq6C?Sc{=CRs5PFu!m%V2cS88Us`!KJTe(^tE9dUf@_yaFz- zmd&f>@;0)08$EfA<601PafUk9P{$cIv4%|^!{+e>)ON9*<3vnjJjXZJ8^N4(_G zn<;sd4Ra|KQ#mtbb2Ynfpv7pN>6&R{I`%UAjxu|{%Y3(wu{xQ)hna^@EfK`9Y?~ZD zkI1&8PuBhNF9{-bhaCEqU*j>g4b2xnwt1p_gO_h1|n|&~NeF>{C;q)6={f23i zSKlg0t7G+bUj5pzN!`pEH+zitA-pOPFpeu|Vhfr)1aQoX0w{d+I)qjT;W=_aP1Vut=qw_+uhsiCJ*l>Fxi2-HOD$qk zi{9#I>hAXx@9?DVgw)b|gz&*{1LGq7;zfEBlZ zuxz0r3&@{q|kG3H&^6#46HQUG>I?Qw(XAbu=y*4K8gqQ4J zQp5iIQbi=CF&VpNcg}7?^KzJC4p|sWACqSFlD3;E>0f<0s37!tcL-TRBOcO580n3a zY?QE9hT+#-DVQ>USUi=%Y-nfF_Hbm!9NF>LB?T(**DnVY1i9)Cq13Fx3mYn&+~C!2 z{DU^zr%e?$JB;*3O2%K8G*I?y`;%}FJ(1p3K)g(Xcd7nxF7b+k>OG zL@yCdcgGeh)RFoj5g4%SQ2($4m})T``hsXEUW`!^Cd;r=_$wfQnG>}HrIbQHObjap z7K};dM2~&{)04M9{SK-adZE1okpX>^a-5;Awgq9cp`N|d?a((6Djxt{TEbz2t4#Kiq#bPbSdW^d-}1r z>fLKDH@(v|eZRM0D;%Y7?|FR>SJ=Q7HgJVo*upJLtHV?1M5ji+FFlJ(FP%#-MQ3?t z*ERhEO9~>x1PTL%+hk*peYK>PYl_e=N#5tx?ninXt-sgid~Mf4qfay!G@FQDnDos} z;V6k{VvLfAyaJt#sOV+~y$h^)ffoz20v298yZGuGw=Z9rpLnj_B*V|}WpjeW3Q@Ep z!k^;5heCEVo1h(`OW|~dbGkyGHW|i77A@CunPT|Vlb=9KYrQ&wA(*^cK-dJ_&<1g{=(1vFS+?xhuMB&cQ3sl zK)TrkdMZfiNK{?{jUXo?cLVf%M0KR+gu#ZRG-6ir3Dn?ji=%IlYD3f+pM=3mRh^<^ zIU*KD@|R(Gr1H#vLNWVJ@L)~jb?2I%Zn}7C(((3VuRk`e@#eMM)RO1+oY})^(Zhq& z*09=|se&m7SHGLB-_7iSOa2(fvt= zJqntBpjV@CDY0Z&4>~!jkSqXX{7T?$$3$wN41CvtSX7@TNj4FaRvfA0Lk3$F>q@i{nu=50_m^?z`7{>3Xo zFDff5fxm`TEiI2YoM`ccjD@FvHh=nYV3ng|4xO6@Cz9G4Kt(s$m@_e09KjqL2QD%~ z4531>n8<+?Bqqti{f{|IDswMreDblt-gNNCSoiJj$3MEw|5AkbgBy~5% z1sGN6e&CZZb4q%6gOJBbBaU2m5AgGVwI;SBL=y)r7T}9H`}?uNBQLWewjL<92D(8% zD7I!t`#r=Q5}rDzAZ^45lh+|tC$3y}g(%>0VZR6-dh(QOsspSbcOkdBX>N7X^&-=_ z@l=4+8jvCF))T2Pj>Ct?i<;8lSRW3SWNFcp@e$OzsMI-n4T1=rmC5ZP&=%0H(BtscG?)Y`H_Il8oo~ zj5m+FeCZh%>n0prUNxIn?a8X~q}TYe^0=%zHmh!`4N-ji*_QoGC(wQ0xt4X%S5Y(3 zegkOWo$iO+Jxon2W8CV^-gZ4Ze<>bd{+T4mTukLuc*#mk?9w*(dM0^|SGzWXJi0Dae!juI z=9;z&Q%coe+c&~$K2fwanQ+U%d$)Nb_`fX91OHbsEgJZIL86Q=k~P~5iZ8Mnwq+@P zlPZJoZ?aV2_XJ)!0EGax)WD6b58TKO^uEN-1t(AN6xK@8LDyYOhj*qN?qey$K zZd6K*{=`0rU(kDv7omWjY*3p-ObbB6R+_WX*s!&Vr?kOUQArzMW z>2N}Xjza#ZCiI%1qwmtcE47@O3lA{dj7Sa1n<}2JtW8< zg-h|)$3GPAMLq^DpH6dcCrDdCeBppQfluo0H`@hubvVg!5`oKg4BB}zEOThkZe4&a z()*w9{CRHov5qDzwB6rh2Dw#oSV=odTl_!Zwqa4L(96KJk8gDGo@_TI2shQ!Fc|uK z$a@YvKBi+33>xZqh54ABS3Ud;3lKTP<2sSD&9l92H#3} zc%raX7_1s+z|cU760qJt5y3nIk0W&F;V8b+qTXhYLd#KP8&PiaI&`1)1@5z4O~#Fs z%!y6z3~x&9)smSiX8!};h9f|v!+o@>h|4HrGs@gMJsI_5JCLlRiCME9?zF5n;F#(% zCfeq7rBD`b0^rTLCJ%Xal~?Mgn`ZC#)*bL=m2g>=Y*wWwt9o30Be!UBkg4DCyLHSy zU_N#+N4h<`9`xjz$K!A4(}JzzJSnwP2j)^50N%}%4B^(2!xSQJZRLCRshUdE z3#qmg&^1AAdAc|{ZANECPhaW$jA$ch>_?XZEcBj^aCBDm^p)EXxF3jRc$hf*>i|12 z#+eVm=cj+L@DAAUg|tV2H7KP)ki_}q;KLRyCyu2+u1yaP^V(CE{vNQK3Y-uQ>ktmL zo7BRCy$nFplgN7=JYI#L=JXpVth9sqApIr;MYEq+@kNdp2LZz$`Y{NPlq`GrMt|cU z0K~I@2sMBQf;o3=dg&b^R+BomeMugZkcv)^`pGtLN{vsS#p#P#eepGY>5bIviG!0? zZ)!E9Uf6Sfk5^yx)lxiETS_K037mQrt6s&a^H_DBSB)ex+LZA%6S>~_QY2s5JXf(9 zi%gItkJB1iEy(r?Z^S1-$C|qpZMwIlG+i=O?u&9BX3PAv)Ab(Wp2tI}=r6b7ta0S_k%N`)IQRSn`vt2;o z!&mYEQQ`}>6 zTSAO|E28UOWLXgZo=$Md5u~s-RKfj@uEP~BQeaEP0AUx{1wh!zLZ*Ur?#fr`O4lX8 zRRd#H4vbZKz~;PBLIM!BC_D5_!Xq*}vS$Z#KbI1ifYC2Kbmm35453B&Si*4`7-6g5 zOSlwx0*tE06$3Y+4~f{|uOVc&3{Y0;gAVtBq>xEcl-BUc8zmj};=Sas)?bI#Q*i4= zp|d1<>y#d~8k~nzDve5~GN@Hg67OYR3b3>Bh)w~_EDX~iIg_G}2#+J1%AsU<_#=Kr$jo<1)HiLTnXC)Ub(YMv~&mDLwy2V}L*nnz_L~2)N*g3dLH$NREl7 zE+0}1w;9CRp|7NN^i|0Ms0E?&Vkr6=hM-CCp8&I?z+Gv7*ij@x2s1_s-Ut8}W-ij% z(19G`pk#SO5=6xwAd_1gBF`cm)Iw_N5l*ZHa~K=VoDndln92}IZ(OP_0t7^%6#Pj_ zMQtmp>0^x8gHe?nLze3;%ybftU!Iic+QjrQNJ{sclUWQw~we1goxlqQphp{Zx%))N!_G>j*nP za`)5wgLjDDrRpvednMWD@OwDo$xX*Qc8MiIZ~OsmC^h`_1Bk(M)}@44iCEjQR}4|L z*tDwdOah2h*I*IkS}Yn~_iAuv&^fiA02*SSSPN)~wWIM8DD@7cn}MAx_@f2VI~3kO z8SqO$W$*(6TH9fMT9AB}%B9+;i@E1@;kV`GE)$&cor4gFL{kl87a6#mN} zA;Tc_lQW3k7ibF_@fF(x{7%sIp`U>`W--E#c%XYx;vuGD@5?VC_fZocbIj>*3=TL! z1DTjVduH+Z4@}AhA@_eYEeIAO|K1cxS!|-wHQfXML5EHgbhF+yfcK*8g5=dq$<7ec zIogkZXC`D&BDA;g#7F+8K4~z#Bf}y>(2YTz$Erf7G1>C9dQ-pod%n(bL34U?<4R)qXjqCOMi@FDrAZ_Og{@@Cwf>4wkq?l{}4l5L@#69Y#3`V zxI%);QfTi{@PjkU*Cz>p2)Gr5-e90Vgj84>)J3Tl*AmV+>h^Pime zzk0!c`l=wEz%%)dFgk%M|2yaKBQ%F-PCgn*ye6o+fOrww(8ZjWR0)#R@~IU>X=Sms_~{&io6X zH5k5@nP@k(V5*SFYMw*=LPsY3B6z&E6|9JQ94+|J6!sHUA=F~+@5eSm&5u~^yk-kn3+)Co zC9q+FOu@nlEENX?MGa&o)Cq`V$ERb@FrO@qHG`Ii^%x(sySaY|v`){WZvF^)=a6?E zd02Rb=%h%kj6{KRHHNa@M_v$9F%OGZqh-fTYxs3Iq&8AV@$#ON@JnJCx6oYj+EBQ? zgMJV}!D=7)5<#Rs=6{LaeZrP|7t*6KrlT^#zK1p#52q+x9ZSkbWl}*Fh@@_&7X<7i z8a(Oi#_sn;#Q&I*B?zv{>!vo%WO&PVj+b-EMQn1BH@SrAI6SuH#yVhn&F=KBJ2>9P z=}oNO^L%+VzPfs@u63@ib%Nq@tLAd6W()t2-S(wUU6A~|Zb?rhXHFcPODeo!thgtP zGGBIS6foZaBP-AaL+PBM-o4A0lk3aO_GM;W&))fEN*r!I1GRo=E~zMN1VYaI)}3xuSJ!(K@fb z9=Mtrc@qw%sNS2t{zmy4X6<%Q`TgUqUcKpNUcqaRz4REDw}H*uFkQ$s>}DHwd-B@H z@5g|U*$HNkf&l|IMaf2jh{@(jySv$|uY@vz_(Se|ufF^y;&j%t#(K`Ul{Idi>GK%( zjO%=4@wkp7i&?VRtpLpzADPFIr7T(MPW6&ip;(f;+5ON|zL#8s)2dmr`uAk*&7@UK zPK76_;#PWY^f7?F!hho!Zl&fj`RlKyAhV)&wz1u_VGpyugUQ?LN!8@sh`HrWhHmQPgjWDk+Z8Z^D9OF#iC-u@Mf$?`!ur5aWg5{que$_?`w$ z=X!-#QPx?f@Ybmy9QLe4tXlLOy6c0t)e73g8>QkV8&rsNA$=yg2#DOsY>0n*-E#2} z1|%J^5~DjpW*aU9i(B4>|5mv$l0BoFDZ=44qNJ}Z7naVhgp@{YkP!_)6t@v2edRU) z7P`YD9K9aWBhpY5ei<)nx(&UFBV2UIp;ZJ8ZlXKDLMJ3T${_B5TNybSF&qP=J9>{} zhsA#~2}@k<$P)8h!5=-9l0BFtj~SIcDjSXgu?l=8B)J9~+$_Fudj9g`!h2r$5eC{0 zKxrrP-ay`)$QuT)9joO^i-NwbP+#~xhGn2cB_UIy#Eu4m=VJu1J(i;j%}kXPna+k=eu>j|F>&6iVH#TzL~)-sC0k^A)c~wm}8REH>G6O}kn+FJ+ny z%QR~^?Rr+b9?20k8w5Gx6Rn$Cn}}aGX||daU#Qc!7An3dR6*DzYd4W}Gg`5@?(UO= zl(QfGpn$f!`yntx5B$79YvOyYE^6QWUEw#cM{EV)v7z&R2o&$oiv3i zqX2$VtED(EK)D#f3arV+bKm_D>@0)-MjxxjT2R&q1_ z7bpUofv_XK9^iva_Gh(1ro0q-q1SK?Kc;y}|i<$Yk1rp&}s{(k}P&_)CR literal 0 HcmV?d00001 diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc index 23558b48f1cae2e4f0d6ad66839dff1ccc031925..eb2db67ba4297404e0d8f9269a12919abbac1cd0 100644 GIT binary patch delta 2108 zcmZWqYfMx}6yCF@$bz^$rGPw?kVMDSMqym!C?H}h|TVn4Qp1&DDn^RPfM8d)U^ z5~Dw>V!>hzVAU){jDf6%O%Y=dtHtjOhYefV9N{Iftyt+34R!dYPpWTfldgC%+se|K z00(_CrsWAgo7J%uf|g*CPB4g2jY?)OW2vbBT$~&ALh` z=J~q#ZEEnOH1j-SA76!KNLwajX_ zDNK_4y-TgK zEmFQkvP+#}dMFIfSnUteN z6~&ZXl6JooQK@RArlO(kZclpP(C;7Y@J$bw-g3;Cr=w zRTRKg{BreU=vR*ydjnk8da8HL0Vu#tYv;mE%~#icCVbmE6a0b)*L?-0xPE;i+(%sB z4Q05xB*E*USeviGy(Nc4(6qs17@`TQ0Nl576`tI%9sa~r^ZT$7ADHLC-&)%F8~gM+ zJ^EO#fEV7dTm&ENVpm`?E-Ym@C>0t4MZ@yOktFS%^w-Gpiy6a*zw0DWV~Gp`ugxio6UGG`~ka2Z@;A48ad=Jzhzqor87u zvxb+*e0gNi!%96YCYqY$@B&D~MVrq+YYQ)gbS&c6AXCI{KrZ^bZW#(ld76~hJrRdP z-Fp8z;qcuAhY5~gN?I@`dtza=TH-kh{#r|YL7~?O8npQAsyN_kQB@Cw@1WUT1P2IC z5qwS1r7`nsra=R`YuIorA4A2M>0cOeTIw1jN(Ti>qpr zz57O-w%S-BJY5?PPW4{xa;OiZ)DZ;qqG(S9k0mRPAfAA(F{kUuXA{s3<8)s*wLYij z=G3};2?2E^UrMl?AdjGspqQY9z(P iqj>luyu5n>grnb{UyW-G;L~hLy>pb5H&Gy4%kz!`D)V{X%6bYU}b+dYpo23i03oO{ms-t+D| zJ-5C6v2ygFGH4(uNUh)>^NRQCG$(`p^#i38n`s+NQue z>YP|F%jbSBYi0%9npk0*qCAgzSP}Q~Sus}?1m*{NnT7k+jB)=&V1>F}(E#P^3l#}1 z(#+|di?A%E2>)8X2Ex&jH5=S`vOYqJtYT64P^Z(26kzE!uPT&x_)PCJCacvd&l8x~|?K zx{MAc*fN}(?3Qiv#;kWQKrDtB^{^9@jTa#f$BnaKpPZYUdnhda`JxFplp6~u3qnl) zBS^$vQzRUa1!Jb8`d8@`5x!Hn>zUal6j}tkP$UY%A?(TP*UckIs88ZFZ2XN2wKiz%3=c za2_*Cufql0vP>&2SWyLVN$M=q1N?+i`C}NAj;-_uxW>n!8}iV+Dg|!Hqj7xI*W727 zNpK6X@*LD)an%C&6?azkg9Y=dlLP*s7cS+%hw;@ISRIB>s^@ZDoHocXaI+l+js?Lwo+s(VL#y;!db#GT)^o|2v=4;hhZwc*>gqqBP>ohL&(DOuq?YB!TX*7)yvF zBoGn_bge|XI3nE#F^ym#&;*M#sbV@Ildz0nBoq)z2rCIy1V*SMtc4a%J1y4}T!f7T zxp&R9de#lOm2$iGQ345ahd!j$LBd3%X(c!K7_I2Wi9H0l-_P3F$76otdCFYE1)Ec# zLaN)mTq&Ju?FSe_Pumjp5G4tFrT(@Eh#cX|NxykFNkBjx-}+E>gWM5(uuUI5MS6>J zgadp=9-(PR9OiBR*0)999v*X>Cnr_EM_UMoXsqt?snp|>HYeV0PkMdgQ6v!f6bw(V zaa(OH&um$1t`)>7m3}87p-aly;Z(w~)Vu2!h?d_^eh2w<^7CZrkE9>LzC9Yak5~5? b_}@tI-rs`D0zlnveXJn+s5uP%_pSO5?XNPc diff --git a/core/CustomMouseController.py b/core/CustomMouseController.py new file mode 100644 index 00000000..87405be1 --- /dev/null +++ b/core/CustomMouseController.py @@ -0,0 +1,96 @@ +from PyQt5.QtCore import showbase +from direct.task.TaskManagerGlobal import taskMgr + + +class CustomMouseController: + def __init__(self, showbase): + self.showbase = showbase + # This is used to store which keys are currently pressed. + self.keyMap = { + "mouse1": 0, + "cam-forward": 0, + "cam-backward": 0, + "cam-left": 0, + "cam-right": 0, + "cam-up": 0, + "cam-down": 0 + } + + # 添加鼠标控制 + # self.showbase.accept("mouse1", self.setKey, ["mouse1", True]) + # self.showbase.accept("mouse1-up", self.setKey, ["mouse1", False]) + + self.showbase.accept("w", self.setKey, ["cam-forward", True]) + self.showbase.accept("a", self.setKey, ["cam-left", True]) + self.showbase.accept("s", self.setKey, ["cam-backward", True]) + self.showbase.accept("d", self.setKey, ["cam-right", True]) + self.showbase.accept("e", self.setKey, ["cam-up", True]) + self.showbase.accept("q", self.setKey, ["cam-down", True]) + + self.showbase.accept("w-up", self.setKey, ["cam-forward", False]) + self.showbase.accept("a-up", self.setKey, ["cam-left", False]) + self.showbase.accept("s-up", self.setKey, ["cam-backward", False]) + self.showbase.accept("d-up", self.setKey, ["cam-right", False]) + self.showbase.accept("e-up", self.setKey, ["cam-up", False]) + self.showbase.accept("q-up", self.setKey, ["cam-down", False]) + + self.last_mouse_x = 0 + self.last_mouse_y = 0 + + self.camera_speed = 25 + self.move_speed = 20 + + def setUp(self, mouse_speed: float = 25, move_speed: float = 20): + taskMgr.add(self.move, "moveTask") + self.camera_speed = mouse_speed + self.move_speed = move_speed + + def setKey(self, key, value, arg: str = None): + self.keyMap[key] = value + if key == "mouse1" and value == True and self.keyMap[key]: + mouse_pos = self.showbase.mouseWatcherNode.getMouse() + self.last_mouse_x = mouse_pos.get_x() + self.last_mouse_y = mouse_pos.get_y() + + def move(self, task): + dt = self.showbase.clock.dt + + if self.keyMap["cam-left"]: + self.showbase.camera.setX(self.showbase.camera, -self.move_speed * dt) + if self.keyMap["cam-right"]: + self.showbase.camera.setX(self.showbase.camera, +self.move_speed * dt) + if self.keyMap["cam-backward"]: + self.showbase.camera.setY(self.showbase.camera, -self.move_speed * dt) + if self.keyMap["cam-forward"]: + self.showbase.camera.setY(self.showbase.camera, +self.move_speed * dt) + if self.keyMap["cam-up"]: + self.showbase.camera.setZ(self.showbase.camera, +self.move_speed * dt) + if self.keyMap["cam-down"]: + self.showbase.camera.setZ(self.showbase.camera, -self.move_speed * dt) + if self.keyMap["mouse1"]: + try: + mouse_pos = self.showbase.mouseWatcherNode.getMouse() + current_x = mouse_pos.get_x() + current_y = mouse_pos.get_y() + + # 计算鼠标移动差值 + dx = current_x - self.last_mouse_x + dy = current_y - self.last_mouse_y + dy *= -1 + + # 根据鼠标移动调整摄像机 + if abs(dx) > 0.01 or abs(dy) > 0.01: + cam_hpr = self.showbase.camera.get_hpr() + self.showbase.camera.set_hpr( + cam_hpr.x - dx * self.camera_speed, + max(-90, min(90, cam_hpr.y - dy * self.camera_speed)), + 0 + ) + + # 更新鼠标位置 + self.last_mouse_x = current_x + self.last_mouse_y = current_y + except Exception as e: + print(f"旋转相机失败:{e}") + + return task.cont \ No newline at end of file diff --git a/core/__pycache__/CustomMouseController.cpython-312.pyc b/core/__pycache__/CustomMouseController.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e0aa92be92e9a57cd999e8b6e4664ef64a58f22 GIT binary patch literal 6345 zcmd5ATWnLwb?)`O_H}G00i47kPB0{4@lq0E#4ZZyW+8zr%Q_Uuc1zIKHNH0w$Bx6? zn@#L>R{Md}qz}b()h6sigf1;v(FZG4YFhQdUstNSvJ+Rfu0W!yN|&z&LLW*$+B4$^ ziLtBTRuw1l%sF#r&YU}E=FBAQ6d-5BZs1 zh9NlXWBr<54TD(Jg+$E-By!i;)QDa#$lJB|2w`VbK^_Q|j>C)F`k z@@Zn_03b6+LcNTLdRdXVfO<8eUSuUs)JR+(Cvq3~US2E_wUV}v6?H)AL;-pUFrPuK z=opoifZrJym8Firuz~|VpM*VWrRXFAlp6T0ya*pRgThQ0xhR7aBD3y~eP0J$Cc_RR zZw6gcdo#+fX%jWjSG%Smjf$Me_p{f@`~VDk0*TrJ5_LsO6beYx7cH@*fJ8&l5{(5U znu?ZKT0ml1(GtrGNaVy#McR6E0g1YzC2lDoQD3w~a{-Blq9s-okZ3AeVr2n|W$Pu1 z#0O+t9rysCvS(VX0xElwMGH{6)agYii0swfm^c@^PSyH}(`i{Xc-;OyeE~e?#^S1x z5a-;UAxa5^@=1Nls-9qYuzx^V)e>}cWR)_-z}T?LjA1><>`@svHUOeBGA1rsWkhVG zwS-NyK95VG4sl~3;An_*Llxp=T8gxkXn7vWd)3hMo*4?efQkQ6wL?cfa%1yH`GJKGN0f31F!?udi+z395q2H8?z|xLl#S{CzTfVi);W z;Gf?{NsDcg`-W**i>fTi8f(&8o3vCXtG6aAs{pxQCRCLG->_^°LN%9IeSF~K@} zdamKx?kl@*m==_1b7x#Qk`#=2*d+^{(dI*O;c$L-=}j)$d?YR$&CfP0^hBEv$Aymk zY+<1x+T0!&Ud+$V&V6T{YG0h|xpwx-*&Cm~*%)=a6c>)IADv;hE{sMUkE@npzi@MZ z)Nw2>yqtgZ!JB;4aWpP;FruD#rWM*xm}_Bf|nNGURz=maia za>%pt!z@tQ5pb;8ZdD+gy9Z(wJC6xV)wo4bx4Yj7RGcVrr5BGYEG;cZZ> zJRGT4M?y9EcQIw|hJJEA6mO$jLfN!o$`I+CId<{bY$z@~o3uWYUZ)1KPIx=?>jp(| zzbC6LG?6*UZ{=@LKu7~$lP^#SGi|^RLx?l93v`yktjJQ#Rj5Dl8TbTn^Q>{c74~Lb zBMeN#^>jVKg*h+rYdMhNg9Wlq7kUy0mji2hX6Ffl6DEIlO*bZyT@QG-DyUN~uAFYE z*W8L{K*HStQBs9|NpTWKt@41m#_?9@oWg^+25_)8v8bv6@2K)`xP7A%hSfr1RJNVjL9m6tOachK9xkz@&FXzr)t1YQ8RbM1WVFT8rk)=_Sw;S?%jzS=AYJl zT(fX`vE@+GV!dp-WJ*|`i&>saSlVNj_9e@q$zw^I{aV?TvL#!~WM|x1yO#XDv36O9 z9EX|NiOF`lQ`UvO-vq#M=__DJ3xvQaxvkR6u26hU$tM27B|mkLNH2Fd}iA*TVz zjgN+&>Hd=Gq^J~jK4ch*Y-=MadC)TzsXJLT+zpX9zG-$iwwdJ2!+fD!b zwcW6`+NVL=4WBX{(Kf>17JZ@JxB2n4^j19Hmht}+ny|)8oO}*s`5f@cq&bk~^I;Co z0rC7f5P~p!LL26RORo4Fpj=%T-Wx)o@q|txcRYo#_If%V5%oiz;Ok4*@O0~W_??ZP zK`*)Rb0GT_Bz@1}5z?E_%aQ;i2f~84A*Tt*FnMO!`FK0=r{wz;dxj^LD7&+`0i=B{ zLp`d)@O<*0q-8vGrWTEBo<_h%u51)QY~mux4uvq|b!6~I^=XK0^0(I`r76>|0pa=y zz0#7QMN?S+9{PaIScySr;B9L{|CYXxv=n!Y8AIcmGiZ#lmv+-UjV5O_Q=|DC&CzIr zMkAAZ{{rZL)BK~ZKZGu{4}Kr0d%d6p%^yd}cE3CG_0N9!gFnBwaQFS6|LN+-pZ(-} zfBD6`_N}QLLly=N3-X&6GQM`#e#9*190)q&PzD!c{y%CMhOanzb%itN46*8IT~ ziM^jE1AhbYD1=g`P1CPWy`C`HVkTR{R39_dPqKFmWzz?z4n{(Yh8;;``LuJ&nK0U7 zMq9$z7&A7`iP!vB{E3~dv7N08J->GS%oS~WUX<4XqHGFxTZm$ce0J1;pC*4CJ{HDT?DSv!`jMIkNz1m& z`b+w`!}I2Zy)_2^4XrUt>n%&oY{%6tbEoF3eq8-g^^H>t4T;v{G5FtqJZ3+>wEdMO zixZv?9_qM?%H>kj*c9R7<=c}DduY8ZpmOtca4HxTER#wa`Nu;YGS@ypjA`Gk@`|Ws z&%As7bhNT1QSOMAJMNTiN%#BbvJSee%H*Q@q^+~Tc>w+HfWEsy^C?r>y-oA!HXd+0 zi-~8zl~CB3Zo8hmjSR!dce&IO7d+C4qdtNgU9R(^ZeNOnZO~ig`+Nbnf}bI{MpiIQ zQ!qJRA0e6^&=eJuD4rH1sga4ZxPTt8^?dF9ml+M;I9IWZpjxO{A=Uj7o_EZ7RuEJx zrFOnLC5XuAw)3j-3&E4h{-%>k2R!ej+*YkPh$WBGM7%^3$)=s|VK=-5b@>A4+&-FE yr&KsW^dQ$ARclpfOMTl5_!P8}%T*>f{<4N)nA>RkZPfC2?OBGYet<|x2mTw1|C%fS literal 0 HcmV?d00001 diff --git a/core/__pycache__/selection.cpython-312.pyc b/core/__pycache__/selection.cpython-312.pyc index d4c09c6ce9cbeaadcdc1f208f3ada51704388ccb..095a7f8e748aaa7e704bbb617ff0d52269b543d8 100644 GIT binary patch delta 20211 zcmcJ133wFOm2Op6SFhErmb$vNpru;Eg-Q-0KJjpZlDgIA2>WXLiTwGq=wk2Apu0`t8$Uu5ij_Sl*C-Z{5= z6*$Sv`(6o7-MU-dTXoNWmV2td@o$R%yucZMVKC?zI11K&XV;N`J!;Hg4;-DlwWiO! z-P|W`7g@&1I+eYaKI?WX%P5%DjFW$aajH%$uo1&-Pajr0G(+Q%)R8|ZEJx3ElTvmi zn@R@RH}qjsi)+xcYpc}lA-`HOhmo({)ZxuftyD{Od~0=jHs{F|Cr*>oKjC)2(ol zZj;mWIYrX)B@b`0kbkhH zvLBG2*)}R`A(J#_S=n4tW51KFBPZ>(yu!=0D~MrZG5J-cLCd)>mVm9%w%l7l7-$X%w{#Nnka^_4AiAHcnzfF8k$?w_-L!Bq!l2Mm0a$L zJ=2~tI$Nfv0`6pC732}^ig}6x##2cL2BeUY>^_wzG6#ln3LbpT^zf?4SBo>}D155@ zs$qqbo*!f%WcG4*F%Pm1!_X3PxVY3}E4N)a_q}URJ#+2MOVg)by>@8i>WLT0>&4|| ztzrGpptHl{THU>`Z$K)5Ugecfd1ek?W+TY~LZp(0hAer()?+5E9b21{8}kquq{T>< zASpzGtSW6HAC}D3x?P?%gHl-4MFd9{saz(q^GS)LV3|Wrm$B4>RqsR64kWDVboF}x zV)TO@05+cXegvPDP~t{5x5ML6&&3bkL&a8UTA8{f*oX4~3)=6u2GWpMggzIL6! z{cK+PIyLumH4o`dmNaLH=Gz5^IfhbH(aZF)K9*&)YmZHaiYn*I4Xi2-NNFOcAaH{r!Xm_gr`nIudt&z5x-FnENMGvH6;xt(gY!eY&mF-jyLfs{n^%X#~SQdjLbEuZG5JD zbfyX~Pns9ZW7Ba=BwN(WQ;LD1PX*u+os#NnrlgXm#K8;2dz1EU8WrGQq&f%hDq%%z)L9LEbzB@}22vjEUjqSZW2PDp+Qi1IdUof*w>d>J$3O^sJ7S zdG#n6GdZ7rzkZl=(sP1j%p&DWY&Kb;WTjMS)f!gqZSUCI?Y<^=kIws=>9ItGt}fa$$YE3%jBE9)p71Cc58jO) zafu9AzyKCxMN23aXcKDcJ;4tOd%RbG6+B_%y)ZSeeSRTEWEtK z4Q$&x09r9z$(%aSPm4AU40Y}5ce&lPXuZqR(c9bIDPMH>BjZLeNZ?ytgl{ny?;O@f zXXp;=5D%%+?G7t39oFrDySI1J+Z$aJB?kH<*P3WUSwlyJifHI?lB)f9hhsqY&E4$k zb#;2W2l_V;yJ21xH-iqv94`XHQXrtwE3DcjzZDjIWXU{kKi+)2W0WQLo~#LQf#yKR z82fjP6JqtRXO*#mg2Xa^QbRUi4}i8z8-AizW*A4-&IpVp^Vo)?8>YmJgcFoz8b{X6 z=$P7N|FGee4Hs)x-eA;*#X(c$5oOR)d8B7Fcgmb|+>OJ=QM~M8 zPUDd^Q(|^VbOc4moq^Ixv6fzz1jUj-?xeUVRv0Ll6f5Fo=1H-9O3Xd(35tawu`(!D zj%8dD8)j1AzMq*8!_wwo5-Pv=FeQUb-%(gZ_50;Q3(LHh(p&|n_gQVrJnns6dP_0) zelZVeS-;n;i$D>1I4zyLoo7byP!M~yo!Gj}=nEic)2!sJtay6Ie5!RAJ$iU|^Z+3( zMAdd;ueI8`Vw?|n0enG`g7qo93cv`hS3xR&AX*W9yzD;cA+D)F(Q_4cuzJr|Sja@B zK$`E(S;I0H5cj->bUO2{nYz_;)9WZ92Jj z5~rg@9LbPt`D2|@PPfye!PW}m66;DwkQ_zwZ6qkc!&+DW5IEfsXOdAsHocp$VlxnM z1ET9E%yx&hF-Dbn4`m7Rp0tI^+2e4zukhAfmn_hqLYA9>>S#o#X_vDg3J>8h{3B-DIyB_-pApJ8Ei)a+gO7I(J!P1V@miL|DW&>R$+soA@njo7`* zZ7UWr?-ewQaQg8gZA(7)KAYZ>!@ZxwLpn)f^DuLG*UYB^JcpYlb{~k#KBZ6D$NNlt zpUS7|Q~T6?8lMJa9I>s2C7h>271k0{T+=ZHBG8jb4u8W^$;hX z^@3;U>bV0SpL_PoxiizJz9oBxWO1ko3!8U!fWM{Le*ts-2$26jiEU$J850-UJfP zYwvas^ukmrXRMM2`Zl@z=;)5>%;7n*yjtJg-wm@#W}t9xJ7~Nxagdg4q17O&+x?!n z#vBvB(QfYO^*}e#+kwR1(b2iv5fRIgL4)l4^P|4C$i+E54P;;St$9Cr!OXG+a2dKE zg4O^9ex+GE|8s@Hu!jAdLHa4BAQRC|RT&g2CxrzO9l9(iluZg1k*oPZVg960bp`xW zX=^VD>nQUwoOV8C-LgPiV8uilw63B$H?|E46+xlms~I&^`qGWB_F!{HN^=@>9`K0c z&d^vdFAmpkRL=d{$l-#E?BAhS2#Fk#%Waa5u+=&l3eoh8CgZ z`>jz&p0o4hjWm%;zr^bu^F?ymsEw8X(wGxVZ=6Hhn!u~T!Ojz+fwE8Q(Zd$#CDhd2Bs-06u%kh3U<=;74Ik!RQdNL3gu z0XduDQzz6Eyy|460(>&~)ce)L2Kk(TPsSl|%QMqw{7+0j8o2)5?_B%An=vl_!&^^W zfB4+=qu=`Y`=^sR9vJ@ABiD|6!=ZC1!rK0Ub~j+rz=L773a<5V-P_%@%Y!E9d(e9% zFQ$M-@}lir64U}7f@&&gx^p1bN(Yx78HQpVbq#g5gM!)zY#Ytf?ie2IaJ$>3!DxJxxj;}n+m%4v8X%~<;302ly#+b@=HCQi zvtinw3}^2lez}04DpRVhxS`bN8b?}ZvY5Q`8w_ixII26s9l0afH(xM1bjef>UUNgm zj0V6>)_n?sLc!=t*%=_`y~P)@b2iZ9(A)Cy^7T%}=tqG;yf^iz#? zm4o|fs&Q2z_tQci(n;DDzG|N+3~7Hc!A}zNCZ3a+0etOz6Ew_D0XU`^qhd5+9C8a z$_mZZ69=xHIWYaLr$CF74V9};zdrreVHsU`DXQQ2Wj#iUt2JoJNWTQKigqW%irk&t zQIaxcgNib;+@eYhWm08mh~6~eo_<-ELa{R2yotFl!|J|{-7dr%?Rr!d(Typ4E@SFZ z1U_QZO1J~PLDt9i~BHWK+ho2%x3_RY3{C)Fo0uO+>p}|{fa5IP-=mbXGuxcAV zSFnb)>>mL!*0Kqh5V3~<1FB-YN^yexrrGKT(GC!`>wph6zFEh5Ja_^_|kdAl>z@VWF3F?tmhaLhaw-XE-!g?)grIW`i*q*VL zpgMsUU0g)#g5`RT64(T^E}zoJQwM+^8lJBZ$*=Nxe}exJ%zPWDAGjvy;nXCkA39(m zbRf}#vXCmFrq-*vN&V2#J#d|qo)cI|KZI(`^jmLCkG%G=pW6FZUp{>0><_`-2Tr>7 z%@?no{-(?Ze}s`U+aUi1W!$?Li2;EU%4oFCU2>f^%#67KX^IwaPbiMp!HWJNJ5ZSc z1gCf@pb1IQ1<(gAk6pNE#n zUncER5Q1seV|hpOMvEs(m%YnQn3hk$PWqqEm{BXUjU%^%%dWPWy}@vXRu=tn+T%5& zmVgF?-eoZ(BrXVw3&!ftI^SJ+q3V*jen#!bil2ei0uHp=OF~_gUzW;_gtP3Jdm?Rd zNLUgSmQZ$C`lUM?hI(*uvyFMj4&=NI+z#A%hp~AHcYX;E>CT8~fNBXY|C!k$jR%*% zY8!YE)ZjtTfCoVf4g?)I5cFU&2w*Z8`gA@c-4oDyxhEMP`qs7H6epVr{lb}xoRxS6 zpBOGyID94=ZUb%x+?howCwm;cEU{9KR0tf~Ge7F<$DLNT zrxM4YhS|~~o~#vX09@!_bb8?D*8>*R$djewbe=EOgC+$%oI2Q>Y?~wcJ&U7R-qh&u zq63Ov!(CDjvQE7(b$@CCL=U3bzGf$G z%K>MhIwN}&(lA9G3~|*twRvdo@@~&yZ%2RBE0S_M+Ay0s`UWGu6MVBY>P<$Kzrjls z-mA7y5JSd8jsGE^Z8mJ2^2-|u5BXXhxw2^fpV*p8+&?=o>3?Cpe+LrQclNqEB-!Z` z7U)I96bKvWRn$~33vK7iC)&1L%((+h6fh2~A;BIL?2|&llwb)7Xa#`Bm8Mb4 zm=ua=I#$OIl~5y?6zsGB(-XGJkgYaotDUG@6{>3s*0oJ+zVo7O>!h$PR%bLNWUC3< zYR1|_wd;bl>n_@En-p%R#o0k2`}o}f7y}qEkugMU9=J-diQvlahry0Qu+m%{eBQ{~ zkdPY`a!2`~P)r-s>MOu#=^O(e?m`)O6CzU*;sZD`?B6?sfCPouyeu&=q&0qFwCH1|2ekTVqg5ddz$qOq7l?$R zJty{yMbR-|*(eeiC-kdH=of3ic;hAw;2RAoQAz`m*b*2s1PsyM6Z(!6Mn|1CL`R*X zNOBsalD7?*zcoxq5O0oz*uPK|?mEKl9vo1GXz~pSuQCX;W+$ZB|iHK~c2Z z20_8AVPR z%sy$ViRGjH0x_4dYqWhv9~xUZX=;e&l?6>@fjcHm)t6Jue^sac|1Z7%^_|`ZW2Ht{ zT@1@u`N@j4V;y6gC(`OeLPJn!pi1Q82!5?GtT8bcOxiV7+=WcznlkP}8Na5AEXztC z-@I}{$!;Wt>ncoi!4%@U=s~iZ>{wT@`9VygqA6`cg5Cr<$`)e};vu@yUcAITR%sYX zACe{{C}3s58rfMGAXDpd8t=pGb|m|e_<@9Vkzm_hgVNV;EBK)1zC6;FZq?HbkS!hV z-JOc@*4ysm*gfQ>)?7cGM+5eXVdHXiFr5}%^YP{Gz*1y-sT@fel6gqzkUfw}3~v7q zxZamFTO?EMgpT7du6CRYS+~Kj!ab!<4g*`Qd!Zo7$Haj`t_0N1hzGwEa1i{$ds#2P zhmpo1x}56X(o4}4jhWmq$GSvT%AHC%V%9P!oZlNivOn` zFs>D1XIND%ibe%{ju_{OTDB@4#;Sx+P3rclN;IzZ!|(@)odr*a9$;Xv1OGn8snElz z16P)#*k;p$lJ3Wcksf^nQW_}kkp}}&099=ni~0fmS>`ULpOeGRVJ|>W{aN~WO3q2Z zd10uUyuCSNGhlkM+cvV%m$(zDkacMh#zv!Yy5v#TG|5=L7W3C3K_8d&3OT!ZuHPgp z!Bh#S4QH*oxc)hTjd0^#`T)sAB&gcX<{R0aExm|ER2Pl+eH!y#MS^&ZUMLgbwS35$ zq!+%)DE2>(i?b~P#dEZl-wLY%NUd4N+K;wRLF`HV1VJZR7<3{4%pTbnOtFvljENr< z*G**?k9xqVbVPgElo>MR2TkCH4y2x36lgiQbj&jLz$+PN3t!1OJM?bb>w7OKU-w!F@TEK`mIsD{ zV(ov-fD_L0Ym@lpca;C<3kZYJTE28w!m~cO!oH%I`EjwfrJnoo{PdO@?tS%~mIB55 z`7EXj6fJZ4_ltN;*YHrM6W&1V$V5P;y5gT8A%g=1tuQ@cI-4L;RY7&aToQWNia~NV zrvl^U03JBth&f(JQG?AAP0?`biAueHn8!chFkSRs)P<$=7^2q@3ZuM9C*pm|{mNn7 z;fHequc(F^q22YjMlnj|+_TeZ^)7tmx!zq!s&k-NSLUHQ?ol&W2NIk$3T z1L!ca`7~M+0PCn8T)6)ghP!OaI$jer*+M2q(BuddTryS6@bYs0;xi3h&8&8o`=~w1 ze}tCF!DdTy7IQvJ+q{@NUtnym}AvCH+zj@?Z31}VPC{Qtj51TyGB*FSfgsXMlqf$d5vlk)+p}qzIEWgvqq)M@UW!c zA-RGC7bjh{2@7*p@3R)B1Yx!GQ#x!ZuRHKyc6Il6^vcW5Ew4NOUtV~(yE^u`{=&ke zb^a9>-rvIwaot&|N7Gt5WpThi!0|le$Oak5{lS!C(0M7M`T@rS?Ps?H%a>1@R{R$i z<;dQ zh6bVL>LB~mg$`ZCvH`nTJKLkF@K)eScBHa6mW+QQ=UUIcR zB2OHLE|Dk9+NHk{n*ZV=cT|T;76nTdT`Xz9-RnBoGXsylAL9ov$KnU?8LOWZm&W(7 zw~v`GiM6<2T^HG}uD>KS{56jKTo~QKW<~Qt<~(18bfI>62KOFgTyEyxGxLz{MCE|A zWTlR;y|YtopptVVWI6Qk{qO7dK5s_pmKd;b0KP?h;J$T=xg2@>z7ShWo@y^-zcv0w zyOjm&E_i>Iz66`ABm=xU@~8W&tW-Yc!<9So{0|(!PF8f>qqq%1^E;l-M;k`|$mF+} z!tDs@BP6J?kzlVG@1fk`hS?(RHqXFO^$6yV4wRJ6r=U zcRPIg*#Uzg|JYfnE`e+*pC$jhGY7sw&UV%+jNJHUXF$oWC4buWqcXanXt$YIvl0lz z*3k)6Va5U^xk&O@@|*6O+-gXN)x)?!D|w_EdbKZdRm+n3J^6YXo;n{&_RS^tcjS@W zPL0CDjX&D6h-GWX-`PFMny7k@Pn~k6lu5qdS7V@KDTj6r6MQxO`#zUq8B5mn=cs9i zl`QG+&sY2{H~#JZF4nLGKQ-WZ1iJAzT4Q%KvR7A^l-9p6&nbA!}su3t3W?&PIs!HUmAFw$uEJ*UOusEe;F;6Lm@;9 z?~L6VzOsYb(Y9LJHnFZXv95YIm_m{b`X-kTW$~#{(Sn;aX$e8tPS(e3(&XKEE%OW*NM?XqFExWRuhA)yHzo z-c-p5xkhgaZ0V(X4bj~#i30~H-=F7AODIoEDmOrRiq|X|V(rZdqEo0sS5*vTmUMjp%H^hk*-E8|`2i zmOn^nTl5+vw9Y4j6lQ(me({NK$mawpoI}Z;wIodye$&`Ao5#9q)Yb zI!hfr(ps`%c6pdx5mxoO`nx>4q-9vN4TxKxa67myApUE^ zYZWTxIJ<8TBH!MJbgZ!us}}*G0)rg!inh6!MS=Qj1jqZ{yA;__HAm5-1(*IMT45E{Mp$ zP_|H!BGuBGvS#M)lw7WUX(1Nc79p)i(tukN`@P0Il|JX# z@X=xN(1`G1iuH(RB74DPO7&EUW26PP>MrZkj~9lrs)O)PUp?0HfqoG*n<}gvQBG>? zm#tYLYkAOG4xbI}yl7oIqQ4?{Q8$^gaH_0gqy;{tcQPM9=PE z{qCeDm-Q(jeMwMX64I9i^<{y(&!%3~FFxD$fqvOkO4e~_D7RJ&=GK0YQcGLTw2XufK^~6clUNZj&7V-=@f6xJhXMjf~ zE9KDwFu&3G@kgz2XbA^^BMYBS%NIcDT+y$6_1RaiK0OALyMFrkl{d~@d;GPl2Ofrr zUORaPewt(Y$>*kDIs#uET_xYT^7e^V@YPg6quE>%*%aCblZ@^O`6*QG#cALCfzK`Y zaxG3AL_7l%S25rl(dU1l$Bmw-lO$Bo5G-go+Y(x`KDcE41!t($6@-6Fc1{XipFoTB zc-_Tg%1~D2ZNbXhE&$m!Dcp5AzkEb_Ns}ib_Wa$yAK=s`7@3sHlF$C8tgaBJL^Zbp zOd$v*IR4@D?uhq44jS{I3Tbv&%n=iYI$tBoCfWZ;2I#5B9;sGrRgVAi5u=eUC#fW# z{O7mLY#F3#RoHxJJ{kLV4(TRy*v#=qNhT+w_C|8*=?Xu+(_b$xV1O|wm0=8LkHzoc#?^y$!dGk}*d5elEgVi5VBuv+SHc6aoLUeb(M0cx3 zusXuH^dH86a;yyILhb zTcSB}uby^3jMe@`hEFVn#qi3BTEzp(@sCg3uP&wYr~68Dq0O4V2S(zCrVPqn=~R`X zjwjEbS_`8052v10w$aA)q1d1fM#`P`p9n%54&qGL#Awi>@eibCW zg{ilZyn|#K$vGrGBsdirfFOX3k<>_xNE(r#%|=Z?=>Vn>cO^t2=}{!#M{tV+)tgFS)TmggCn!Wfp$^ zjfyh9;>JB}2H$X_CXHWuqa=?PZ!Bl?`NcQZu%&#?jcu&R7v3n#;VqDDh8m?AKx!;N z7C2cxd1f{CH`EWc&4D-f=N1q-x^9{>OV delta 8587 zcmbVR3wTu3wLbftc_lN+oMa}G7fB`$l1v_ukc3CVV*(l>Y9eB-1|?0I$V2k*WP&8l z7(@#Rf+Q|0SV{#;OAzJZC`BkD?Y$Ij)l7*ubfgG14|4DASESnZdh2)ZTKmi-A=>-d zdm#T=XYIAuUTZ(rUVG1%=cUb;#mHYrM1%|Y+#|QIPI#jyGLCKN$@+FS6I{%tS!Zdm z)>xS!3bzR^?NfqFcSs~*L8yso(>n~UZ}c4AB--szz;0z`SkF$vy<#fsgbiYsrp@Mv z^(~VgVQF<(VW@M@(1E9iwmm<(v1|D0gCo9EBd1;rw+A1%tf~6WLCq*bNrjqFAr7g7 znsIuZ3URqCi75mQDZn-aSX+R#J2af5f-2?f)>bo?3|DkD%nD`tpX!q@MkV2Y-!*-u z#6E{2V>YwH1I9nt3s6}=zpCC{Ro76j*y$@=sc&dnUtiVas#lU>g(*>&ifqLK9j1C# z2t%e6HUqQ~Wvtv+9w8f0(cLjqJ653#Jx+(oAmG}${!h9SL}Xi24);EM}7#8D0L zwJQI1qID{L2hn6!8 z6UlumB`O)ZY#FQo4%>c$Zr&5S8r?h{Tg+U(8?oX_b|v2-7Tc+q8(~2zpi@Uc zwT-GCWJ)2vT4>hTl^KZ2!w{F6$^5V&^)6Nn-Kp{H9(XzRUSE?TirEjL1U+8S9i>u& zFvgQS4E%X98qp(p!1-bfd||M{@Anx@%J;^l;p)4#G>^2_8md12Zb#CkZ^zN{h_F(MI9JlZkC!yleSUFXrRO@XMc1m=aMVX=@>Y=u{xyctWl zM>naNY;KCZ*qUf`;lGfHOqkHT3GVtR2L4hI$E=3B1Ik&;gw6t zVa^7-3EP&|#d-}M!`eb@BPCJt z>L1s)NiP0=lszhJknR^AWe!Vg7JQJI1Cd$z5M#7-4DIP2-n-@6>DQ~2nb?YwhR{l2 zw~rMKo;Y=F>!y)SZw__t9y)b$=Ujwb#lVneCA31&a+Tphl2T!2vj|NY} z;E7j<&OBLaZ>8C0w~y>TOl^|NjyJEp_2$U;w+-*xjM5_qPyXrW+Xhdc8F{XE^wh3v zhxZS1b*(rqWYDYKK|4Z5+w<*eQ^P;pGt}KPc;cCn&f}`g;E9dbw!Ds3((-OC;R;6g z9UnP)V7PbR(2t&0y-P>WQHk2*;ECta6tw_Dt*Wy%9ptP8`_PuXgQvfbzD%1oEnsYI zcGbD-Z(Gye(9~+@s-e5tX874_L)}M)w)VoRtkQybb)n9|V&1FjnoOooTItGMf^-6k zRmxKMC@U)rgXiLAC7^2oV|Idwak4Z!JxN(e0*eqFVSMc>w-Dn&f)&u59hVshZ?047 z8{M_5n-%3&;!~BDFa&$(QD(sI?EJ{9R8WMSvQfCMnFCc3sRcXQx3*uBWBP2<`{ja* zvUO*8Pxyd5)h|!&m$NP=rFLtc3%_bfb>B#FCj8EAccOYS3LhtG$YY(pVr_MgH$Zub8F(vE8_7~cF7ayrSSm{rhc_Q7P zJnw6nf7Xf#3%YgJ4MJ3mYD%hKPTiZ?FFVMDIOp58w+(%^B_GO_gJgAOzkK_3osgJ5 zkdWt3fR7vr9aoaF2a=}ylcrxtD(ns)bBXK8>6i1lp6b54YR-H5Y!7@WKZx~jYAki{ z(q7gt=WxM0`{nA-bV5XQU-Ybg)9i~TdBBw6H)ZsjGKp>7aeLS8yEFrFdH%S(-iChD z+%W2#=;D4;3GB&9hXs)abM7mPjNFLv!r)s zT+Y41}+C|kvEyZ znc$-K>s{26ixLhRxkCG@h6x@ntD(UnZqUpLqx}ymktQ6f ze^duY4K_B;gn?=4aOPW9W`drgR6aaMmqfrFw_0zacUD`q5!mS>l;4^HZx_eF%a_98 zWK}HORcM8SmyDdY7pLIJxa4(F4bfhGgT-rr(L$F7H>q4;=@!Ybwg8S<65!94RH(FC zAj@io*G^k;XSYU1xS~R7Gy2TuO0XHGegzu}e$hN4L#cX5)!niMp0AEEN4TOVs8Yx1 zOtFnj4vkTDVGLOv`lKNvZKPp|)yfU~5vC2Q+ZJ@26_2~UyJ*~PqC>WrFmpg#!aZXX z8{JPe(q6=$wKHkq8m*x@fqVHM3zHyw#?-HLQFv&4z2Wd5g()}bxLRTj|EE+KzoLT7 zuzdp^WBHBOqz(dg^^PKQsG~%Wkxv_BCwojGg>)>!Wiln?H|9QNtFrW7jG8!xm ze>@TJX+?ZI^F}-#(Z*c-Jt23R0u8h5@l^rchT6IfYg&S*lsKn)LaByVXH~Oy_;QvV z7K+xKQH-dJlXh=FP5Il%^&LbUsn;CRj#r4?z~ew+ML7 zkbzV{-x54#Ehqj|f=swJyCfxGYOZT^)fKs>t!z^2l`T}#NkGYf@@<$lCvJWXkp_bM z3GxvFQK*n7H09{pqNa6CiZYD~`Dw#RR5^kp5|143&Y87hOm9Gb2-{huG_^LmYFpfx z76rnT`VeVcrPS9q2KnLNUDsN#(mY9NiA0T|^hABD=aF`pQ<@!_j0t?p7nmGTrm_K) z(mS=dO-ESa7o|(s6)=?*S#T@fQQcKNV9N5FviePqL0dB1QdSm}+f){mi=Hg^nGV?8 z`9ZOTbAw_Tlf|gHs2!DEl>??UzbUQXlyO}LXXiG}NE?u|{Bl-bcIiNNxj(!7g1qq0 zTFD;SXPSa(OQ=qY-;~m4FX%TFUf22Vnis`7J~IgM83S?I{V^K5#*v zaY;^5zxLM-OfB_KExjO@U6jAB8NDVxvUAC2;X-2OKthf`0aKK3tf=EAgY}xk$j;kQ zQ^M4Nc!xh8v#W2cX_BIZ$j-{^CUTsQ1|>&(@9#HFAL~%gSssgBaWku?-&BBox^n*V z*g;Atoo`zQ%9r}fm-adD?u%abq3Is@a(>>FO{Q8=*s)>;;>TiGZJzY8F}5~K`Z!Bl zo9Em8@Q8+0!;{U^LdH(0|w z;xhu>jj5dSVr#U@Y15+?jj9E%7TZL;Sh-qEn`sQ`U?N;8j)Nl&7JX0y|6W*Zu}!G2 z<@J~2?l1+G)?2hyoa)K2gw*J;VJajR>OyaW)`Xa-aArtho4!qtTL(=T{_w3s>rM@Q zyL4`vYy82i?9+^*f zAfWw1;UWKCqyiBb=y`l!wz6(reQ@g_5hua;T-Wir9?;NM7tpTcJ3=QZNH_A2iGiGM z*d_+>=iuMlGCM9&0KX4`0=O+~?vQELC5$zbqSm)A$hrSa_@khOu|~Fbw`_fEm;1$z zA9TOkGA>UL0-OJ&hKsgEeb}(?6N$YxYv3h z<)xIb*Yp2XxlFQgTAC;kmL3OOFu-;&|T#ID`6$WUPverYUE#@D~(@YgwG zSVf6+&Kg@$Ae}4FR+RYu{#3g@V(fC#VZ=+wC%65?!Y})1as;E%M!X0p+g>R~iLhn+ z07it`XENB+cy`#`Zx|A~rEx{4rxRKQ9 zyB`osM5yn6ep)d}(1dN(Al!%7D?G{JU-#2jQmCv3zaXjSA>(+^E`VoxGUV|9O)8Wu z_(jhmyhPPKyDIuu$RL~QTWawFrcU{#03FX37|)R$H!B93{Wgu$iAME5TO$6A`Hax3 zVTQILvB9|9}{M1g8joF2MJ96({?V4(QuxrYmmcxBRO;_;o>m zkzHx}86^Im0P+jD;tJ6>?}gcn75mozpr6T$sfgbb<82FGwL{GhizB!Vzs7HejG5H# zAJ%7HqC&o9$VdeTY3!wL9PbN|xhGBR6n&L@Rx#s!7{9j&TP$0wJHfs=7D~Ps7pCA1 z0cNaH7;@s^%zi7JeF7t=*-DYyxYg2SajWN4sM;HEcZqn1fLiEj$3L%TtQ@~wco)0Z z1_$<;qqH~kgC)P;7ZIiF6duC3|CC{?RTR3cT^9KEmYfbEJ-Sfl?+y3pl^U1EYos4S z@X)|K;zOc0qSMkTbXp&hT)L+W-bjqekuJR_Je25r&76amnPVK8bHeZ>MSY2nH_BsH zsHVvi;n91{9^)aJEw|&JB4GfQ8dE&-1OYiHpq3l3-0X=`BE}R%O(+*#VRQolkDRsX z_?7T#A|<7$3ywV<1JW7nn$0%;e(c9)jijXT0*y!NqQhlOwRH2Lg!r|z0{PR11BJT^ z<-YvET_xNbeLrr--ss1pw{2G66LybqFz(Ovx%cbEGXC=fO(};-&0~%ttsEmLBY2H~ zmZst(c%9%4g5v@l|LL-19t$Sk3gYUeVp==xW$;L{4dxw~D!wj)>%eE?6~R~2JIZvt z)y1&nBkO~x;R?)?9t!lmUA(j3c!2rQU0u%LH7+xN?)YmpQwJ2tI_sE>D4`Vq; z7wBKYN#F7ggXW{LzQ>Ne!EDi}WbdN+6*l3VEv&*Joy*W7{`liJB>k(T|F8hVC-PRL z6U`&exbx$ABkzV>5*hCco<=%}S5B~yU=ae23-9-X#0UmUXnr#ee|qYAvq-c_zPH|t zj0Ev~I{fKZ7M2U|{VENnp10^oVe52|-^U+P&g)?L`9v1yd-Qy~q|T~pc>dSZI(Ylv zd@7>}<0+AoEFvHAZv4NA{yV{!1ph-IU{@=QKqR0*%UzjpIc~Ju$JPN<{iamhAo;fb zW+TIG!}$J8+z=POzgs*nz{l@ru)U!9AbTF4M!aKr7dMd7Nx3*bO^kQ!91`J&3kR(E zAhUy3j-n;te)F*~(&yO14e0R4fQqK|0liY^T2t59f{PeujpVLtRMwNWL=+Eb+-vZ+ z)|cq}#{_(!okW!rj1OSOO;Z>m`jT{fIeDR=@7_!b07?|KA3!Q#4z4`D3SDayzR(pT zywZ1@PVH6ruf7MZX=H(EWvjw}d*GJrhdTc(oE0zlOT+@n*XRF^K8JS{pLe|9CUsN` zHW)guK+W$8#m^aLQ-)+(uxenfmDd4>vpKr%frGtQb{4i1b5b&p3 zK2dK$xe(!r&mhiGjR{$w-OtLD`v}p+3q*n z`wVua7(To5-KYiBKR4A`b#;114%M-sx(Ri9HGSs1K0}^DPE z`2^f^g$M@)-l7`RW2g>66#lie z!=XQ9&n(lX-Dqd+OuOL51}R6IbmKl|)n?qti_^}%G26woaX0RevbEM5_b{8*extaV zX=mJ6EkX4s_DCL8NnMr0rc{9UlX%txJ3py{KYe27{`0n!+sGC!OFPRKhPe$*uGV$+ z^OZ_0!2d!EIeuMYjD0E^nCA0nfu;Xd6xnk2S79=K$~m9w1UBQZy5%yXDqCRvooD+Kg%*4<6$i#vUh==b0v`6 zq*C&Gm7bi?rI4j<3UYNpDtRkepTJrq7WRyYjMU5`iTOGr*Q&{d8a?qAXJx}YK@Nto zNIl#clm#A^@o;<=IigA<@7CzZ(Os%sd9OT=ap{F|hRJ7mL%%3ywv5c(sPoGEMFXN< zxrP4ruszIf(N?C1H6^-pVP@#S(Dm0}H@4gFS!J9Wem3;OgQ3^XO+9gT>IZLx4h&!W z!9}VIUIeIpDU}_LPHUyh)^6o1H$$_lqTRmTt%I@fR7z}W@NnqBsoZJYSGUTGOb(4) z`@yh4udXknp6@if+`QG(Y1?h?bX2rCIz8*ny;gpX8y6ExEA-=+um9}%I%AVb5!702 zytU0$VQzCd_=i-5ON3TV3^Cw$3JVr_~u0+Z~SXpnT=-Hfy)b=IAns`8*&Urf&v3XOOiz zk)079z5w~A*&Sv}tGUb4+HE)Yw(i)zyOm1Pm)q#zt(zUZ-BMxq-WjI^2sqyWu!p%J zPLUfvQqK8{l*yRopS55-y=HL5W9vRtCi}HT0cG)oQgdkC{&iQBxns)Qq5Kz0pDhh2 zD`tAL$CTN}%tP9tmVmNorgzqua@G(RQ094WNST!Mqw9{WyP_{0)0d9x%Li8i?ZcHr z^8-o~Fx8|?X!J)Dk0c&dA5r^_i{IBQxgmny&u+;WP5MoSlN(SLeerPeu*k113nyz~4l1eUiDqSp?jXtMWBxhW5K&Ov~Jp;yuPrt|`+NmHv&&#Y4 zyTW~7DH4}T7-txdID-2OT9Eh|=HyHdSdoY{JM_euC9Cb1d<_9f41t7`qR1Oe7V~vL zprtOwE4Fayz%eq`FZD=yT?{>$CyW8?xd;}r-^j#Z$-f>8Oj{dS5;g6Fn0bj_PeMH5 zt&By~pD>WnD-wQVEM4JF0YcYD4op3Dc2&a`!Q`(UI5)LtuR9G!mMmFfoaqe0!I=O)j8KXlf6EOcVnt-!Gp#5lG8jnLqz(XE7cJ5T2G;t)x*PvSqN~kO1=V3cxJ6)bLn9vrUSqB!r}0Ll^>+Y!m`Rz+pH$=x$cjIaGIH&NI{8q~{+>ev z`v;D71k^>;ZIlF*r4zce8{$m4VN#!dte9*VT#1e#p_^W3|!S2 z2G@T!sW#kX#2_xZ8q;v@u+Fb72R}E7u0{?6pF4A4{pTAJ8ghXC<(#q`I}Ni;9+pevY9F94ouiBiakQ>HeG5-+G00-cD1S9laCFZoiJB4Ip= zuzdQNaivEYk#9|$d?^u>IG(A|-Z*j7qWvC4L|idl+FK6&Vz_;xiqDARLVzdnupW_z zv$2ubtX~y_o9R)Jv1N4e#qk>+gOn8|=uz#=B`x>pwU+Fk=0qsMMWNc62i;i@^e1_e z_Qo=p6+1WXHZev=%C;pNlre@w!> zC^wHPPQv`?NSu6yvHjmHVG-W_UUkgkid?3MiFnizLbs{@4j2}0S22Wmv8GWRnawhB zXT=olM;$Q?*?5l@&MX{(SS^)O(KIolm3TDqg7h+QuV+QXrT2Pb41TgFF&-bTb*4XY zAh9?68@<-Qjt<^TjK%Q(CY0VED^Tv&(K*aM)-Q;5n@E&DhyZJgCsptbq{{9{Dy*ij zXsPt;D4~|TsII6?rm`k?5fH*4Cv<5~=!Jvf2!|#R;rL?u=u6W-IyMvZ&~(Sd2Bqua zcDC}+lC82qumGuoTLZM_Rv3j)XzIB=Q>UJSn$cTsy#D6#sbeEy8HJ?s&F@X0*mLdaeeQKY1+{VU!u6M4sxw}D_NB=;PsXx^ znNw$;11O&W^Te4iFIgwdi9+crTS>h|JkLvFE{GupZhdC{G;5j$~48=;X)p+_%( zPQs&4j!u35C3k*!`7u+x=L9v~#tDO`+oifq6PP7bR~V^bgv0TF25Wh;jYcq^Z4I_Ai@P$sxG+N@H!zk*6nm%4!CeCUg}oM}6bcAm zoius=LX)G*N{bitXoS(j=|jB4f&f)@#?as+Q)izKuQgu0s)%^r|3ki5@v8nY@v0)( z4fR6v!K`0>IdtJCftLta9C%K-EBsAn_l)G#0`w!$(>X<*ll`_Cf|H^YVQb0Fk^!;B1e{EN~OsfUjtI| zcvA~?7iBlpR?PD(P$1&1Jf=WAu7j^dun+*5lc4vCcfeT*CfJ-SJG))ISpMmBe8-BW zNOLzK)st-pB(w;AG0Y3fG3~-vCyy4$*I_5E2w_!eZPyNmDOpJQ5@|(;CVfG<8D_*b z@j~zlQe)l`6)~9TvUYY0)u5nE$PAmBdCUldayo>hf=Yy8*U`)`MH0&qEJM6R@VXAZ z)ds>i!K~1J;vXTM^crUJMPUNcG-C~`Z}uH>%G+>u0b%Fwp(?Y7`4IEUnt-hK-(|@k z$;v*G6@4T#-GUS|$*(a6WV3NxQ#8ztB%DqdNjjZ0+BaU@;4ktv`jb`!WGlZi+&f-e z?=NcbCp89SE3U?5sXGH|6U|aF*MuxJHiNYV)Wxx5nCxl_A=kaZ>Ewp1>Di|;k7r&< zuNX_O815TSuNz$FolvLy)wv&PQv9ja0nPl0v`olf4YQAWk9dc?<7qX6YbMlbSJbn{ z)UyI%ye$FERytvOK+^^jCUJ62KvR2FnJSdopm268kuF_eu9X`m3|XfX#}!u$q#((%k?gX<^MnOD?#W9qzsx_}}s3uuRl= zo(T8F0nL)D%9JZgF9^trZb-HAa;lWtfO6pnkj|G3^^ThSg^S1aO9s6w zX~tYIWW7>YJ62dbURdWZSVVL5C9r!ctzTOeP|llBCLe0r-*jx@=)HqY0p+5rshL8C zKfG`}b^hS$33aMpJ)4TPI-prIk(PB>N!8NuzNYbpGC3>(k^%|H>cN#ral`%%-ec=% z-I2Z=+d3@wXCV^ofl3?4apE?PSHFp4c=>qlZg2*@hLm~6FLV+-^-q5UIe!F-v zz;_nPH#cR7-sO^@cJXeC1l!s2rlq2H3zM1_ir!r)f%bc>d_$h-y~LyqIimM+B-k#L zH_JrtRVFoyM1GM3+J1$+xlH8OCp8y|{6!LI6SE@K_oZYmTg9&iq4_lk$`F(zz{{7f z06xI$MLhsd&*4B z=g{FqMo^CjpQrMDrd+_4cR&Y^U>YX`r53xby~AY^UwisEKGVy1bf5yTk^9s&tb!1A znH1}w{C(sX>cT4IYLW=DwIdO++1}O7@1#u&G@TM?QqN8uI&UP|nrx0^eTy|q#5woF z;561Cr+@P0mtVFEd|!Jr%+hyMzl&WXLt6M{V-*h-`& z8w{;;-nNKbP-=>7HSd6;Xd3JLJk=n&3$+`R;mx}WDx^VWTZgU7ie*r&m!g_@D}dOo znGNrKy67#|8kxM3C9N5^q*#FGOUc&E2Cjwmoyit+KWd+hn~Cs z_^Y8qPlZM=ghtNi-svIE?4|RNSWtyuh1y(hvwfqj&&nS}21ju~1_N8LkG8hb(NZ#* z-6Y)vN5gr7)aJN!tA8zdtLN8k{=2vMTekYO+XBk_e6Q!Y*;*TrXgV$9%}7cLr0xRs zCBX09j-GjZ26DkGJ}7~%6)uVaU#s{EveVef?P7f&7~f`tz5<@L`Lp8Y6NZGeAC@d|J@L zJuWCh4Y!OEz%P-M3cnj$+W?r7c%&2*f#6|sIFyM1SH%Aa zfthS6F{<`KGh9jMUnSow>EfPdeSa-k$4Z|-Br922R?EH05^vc~DV|s8`E%I)1ERJLMfxUg%g<(ZF8z4OBF-Y%+PLa)kYruYb-CHe(!zsR#jVy^rAE z5IjWwd+rK$f;3d+v+wxYs}8d4L*$crJ#*-0bKp*&Q%Cz=0&ZZzYhnx5ulL= z)nLmTJIoeGPovohu7Iw|Bo#aor4%KylU!J|h5fTnx0scHR#NIeX8XwB>igMN;%+Ep zN63kWEsCg~O39ZEb*ughxyUf8-2p`sAsj~K1;uW+%@UL#tPYAgth;IC9u)6%*t&v= zpemdLA9aOT{^7Hy)CZ!6)mpKHVbfe@&=@tjv#|2cdbn~&|OXtxW)|*tjz*m z>XsBTx^^|Un)CgAZ52mv6M?kY0*RzaI3N5UNaOmAoR#w(TVE{#6~43Kc`ucx5Ty`z zJM4D40pMcY+q-#3o7L&0OACsez0RPd2frHgxR{{46TGLZwX>ZM%D0=rQPM9@H2Hx~ z6ha0Rlr%fK;VV|6b+@(6?J{qN4Zww*4lkqx5OW08mExQVltN*(*f6!A^l)WOoP+bV zwv=$}7rp}<=W?u#T)KComL5|L&X72}?KT%*M{+lJIwiX zn{Di34%yHnp|->KHfvWad@i#>8O_2gaEKD6x(EnB(AfY8=Iilq>E zFnvqN+y`cpoclAm^Bh@p|6V-}6X$J-*dh$!rZY%!^_ud2n<7IWCBNecUd19POHE=x2m zS>!MFxtvPm%kNB~vweV5HuN$MZcC02Ht zZCw@Jy?ik_+xM*m951;4dTxh^YXM#CSlay=DZ z12p;h2ta2@JTbfl9T9fHi;#YSvNION%2h$xx4=?tAAo3B_Ukv zBBcY%qzOoH6>$%gauA819LP6f@DkKH+aQLopb$2`%MAa$;Sjy2Zw#V8kXcT?+R8df|r$-Bl#f^U79hB&4JAeG%cfb99 z_q)qH_@sR58y5FsY;25#zf-pjcRi)`yJb6r z9Mi=NEuEs#&trLnMJOiqOAZAlnbY%orXcIxpFsBeKOH^$?6xzb2R}Hl`|Qs9&i-QP z?CyhSAKU%@&S6I^%r@U=F)ShOg_Gu*tW>laNL&)+>gpi(c?#Rxy#cS*-kym3@Boxq zZjKwXrk%0Io`@@Y&02gxf`8?w>)E*_tjSP(L6=A6Ml^jJgC5mq#Bep_;%Y!^3mOW3 zNo)$|nLKEBm3OP$9Sgs0PJ%DC+Xp3w)!8!%b&I-! zezi#RcB;JS%;?aE$B!2^v@9+u?AX}%f8?bk=Q7%#vmkH>Lg%LB$LdZGY+rQl%gdK9 zJEHhCB$+`lli*r{B7$OR5U~(RhbdAkMbxUqhyBGt?47v?w@hbj4^Be z8`ikHV|T?4yG9a5s>ZBYSI3DXEo0UxG>+dDKVlxUW+MGRt*>fFyj>j&>({KAJYSOZ zl)UR0D}tu{%(SVP{9z{Q5T^Ez%$jGxaB)iV5|>})u0=G-8ZpUttaklq{%l#P#v!kU ze@vfjt0PiC4J10|uo!4?%v5Z|Erwei`7IQ}VG$wt5~K~Y%NGdoT0we{4AUxN!LO8@ z)1Dhh8@GS3+C8D=-p($~8|bW+^b3v!W;Pc}3R9IxVBJ}G6RVFRkF+ytIVCQZe3{^N zssWscrt(%Ano*@$;9A~*3?4_^DC9cHE*@BM{j$}>i$Pwy+R>r9HBQqoqHFrxYDn`2 z16O8b$Zo%3^1C#{;tu+P++gYtr<0AfoFuQ4^hb-+OpVl+hLv`AZ7`r2c6XOIpoZ1< z4jhRXyb3kOuSE`y2a?8><6Glh5+s&=s-&TuPX&AVV)=liHM?(&(zu z`V)yYWAuK@H(NY~>5ckAi+0u?A82Z4LI$;&iI=2_&*0t6CEpcueskD4Xk- zk4pN_=Dx`6!U^9@%0GgE`SX-t6S+%2Hs8nCW|+P(%XT|)b`abFEeq$!&rAB93)5NV z9%9{%;B=THb;~3Uohp1XJh!Mteno<$$|r2JZd}Or!^@RRe*l`Q1M=S` z{n_dUri9483gT-k+7A2O@Kp7k20HH*U-T-o*)&G;gdw^1WysHhCeN15C`81ZGmn@60JK#k5t-H#C^%H>7s+tbmidHrg8SmWFe9%mU#gL|A86UoZyU}D8x8Val6fu_YQ zS3lF#A}f)@7v{C5vqD(eTBZoERd9D}87t8L(0YM6!~qeFUJO>k>nZY53To!m6XOBi zX{&OdAVqt~74W!bc?#Wlwes&AT;1q+wxjKG0@~L`JZ|3=T`o_scZn;EyI)Msp+pvi zT#j>63!7H1VYBqHmCSN<8J82;TtYD7oBLa|^1>Ev zwbz5e!~9DUeMRuK1U7einur<|iTXX^eD`FGb-jDNJjC>0xl3hnMMT`1J;8u_r7!q# zU>z&u!%Sb*QD(x54S63N6uCs>J%aNO`h23^c!>6F4Dp~_4Tr_F3{$v2Y*>2fZN$Yf z8|@GF1hjU4C%$_&y6`Zh;_(kC-46*qLNF}N!H|l@RQptSkLKFwQ$^_xUztQKM$L+p zEe=VZKv~#Sk5>!wh(AC4wc9D{OwYXO8krgR=t|bt`!i)$0|UVoiK0c%i%8*+&#UnZ z@OH3{d7&UQ#U^suM!EQ*C3KrS#`KdRFH=qv>n*6{DLMZn^6Ks}k6Y1PO2`kl@ayFh zXvp6)E2YR9SO6dQPldnrPLjW3aH)4|^4k>Njw{w0?CI>ndm+qM!JfY1LE%?iXrGWu zv>NE4t65XU1{BwpsC@-RvQj2!O4e7?*hU29u!mkoVXVW zipd4s*I!`s(~$D!v*6|a43-HW^%uyWF+FJ@&7#aE!Cg=?n9FMRH7CQJw;avp(~0RI zC`9=7V%)MtJgs;sRBWl4I9o<>8R_8DL^?;0FaEX!u;s_sirmE1C!^djdGU%A4MCMK z?6|uZW4A1!dzV{b-PZhAF_Gv<>9@h2t*NDp$umuYkINs2;b`_BSuyGA0wxCFE0tu+!e$cW!k$6cE*pU@QiSc9x zFLh6bqdQ9E7FmCHM}ox`M|`nhQSi;s>6@c4fA(9*>y0q zJ3EI&MvSJ0wf2zDg-@JvQY%9^7f)0h{AKs5Y2rX#O18@o&c%>K_^T$Ka5^7q@0vFF zDoMsAM8k!oa|2PL`{+5v+X=*5`bMHe4K@XHMqJr?O&#>Bz7|VX8$Vr zVHuJSJd?PFIMl%o##6|h13oxVIGBngFD0lT$ReN@E6*m#AqW#}BG^eVM6i$GAi)U1 z&j}tPc$nY_!D9rY1WyzEj^KHMmk3@bxFnSznkcge~<9*?b4fO=cndWOUe0Ci)G6B2{D$e^Jx~#^z(60bm-BUr0qyG%(wtu zhPBtl1Niz58;N+0;d{8y<;Ih>5TBK}`Al%>&?1|H2K-fmC|Ec$0|FyC_(B;TNmmkx cnFL2iGT0{g^GHL&%`t5Du>CWMzGC112fm151ONa4 diff --git a/core/selection.py b/core/selection.py index 7b879296..3a6f2c63 100644 --- a/core/selection.py +++ b/core/selection.py @@ -3,33 +3,34 @@ 负责物体选择和变换相关功能: - 选择框的创建和更新 -- 坐标轴(Gizmo)系统 +- 坐标轴(Gizmo)系统 - 拖拽变换逻辑 - 射线检测和碰撞检测 """ from PIL.ImageChops import lighter from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderState, - DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue, - CollisionNode, CollisionRay, GeomNode, BitMask32,Material,LColor) + DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue, + CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib, + TransparencyAttrib) from direct.task.TaskManagerGlobal import taskMgr class SelectionSystem: """选择和变换系统类""" - + def __init__(self, world): """初始化选择系统 - + Args: world: 核心世界对象引用 """ self.world = world - + # 选择相关状态 self.selectedNode = None self.selectionBox = None # 选择框 self.selectionBoxTarget = None # 选择框跟踪的目标节点 - + # 坐标轴工具(Gizmo)相关 self.gizmo = None # 坐标轴 self.gizmoTarget = None # 坐标轴跟踪的目标节点 @@ -37,14 +38,14 @@ class SelectionSystem: self.gizmoYAxis = None # Y轴 self.gizmoZAxis = None # Z轴 self.axis_length = 5.0 # 坐标轴长度(增加到5.0) - + # 拖拽相关状态 self.isDraggingGizmo = False # 是否正在拖拽坐标轴 self.dragGizmoAxis = None # 当前拖拽的轴("x", "y", "z") self.gizmoStartPos = None # 拖拽开始时坐标轴的位置 self.gizmoTargetStartPos = None # 拖拽开始时目标节点的位置 self.dragStartMousePos = None # 拖拽开始时的鼠标位置 - + # 高亮相关 self.gizmoHighlightAxis = None self.gizmo_colors = { @@ -53,73 +54,73 @@ class SelectionSystem: "z": (0, 0, 1, 1) # 蓝色 } self.gizmo_highlight_colors = { - "x": (1, 1, 0, 1), # 黄色高亮 - "y": (1, 1, 0, 1), # 黄色高亮 - "z": (1, 1, 0, 1) # 黄色高亮 + "x": (1.5, 1.5, 0, 1), # 黄色高亮 + "y": (1.5, 1.5, 0, 1), # 黄色高亮 + "z": (1.5, 1.5, 0, 1) # 黄色高亮 } - + print("✓ 选择和变换系统初始化完成") - + # ==================== 选择框系统 ==================== - + def createSelectionBox(self, nodePath): """为选中的节点创建选择框""" try: print(f" 开始创建选择框,目标节点: {nodePath.getName()}") - + # 如果已有选择框,先移除 if self.selectionBox: print(" 移除现有选择框") self.selectionBox.removeNode() self.selectionBox = None - + if not nodePath: print(" 目标节点为空,取消创建") return - + # 创建选择框作为render的子节点,但会实时跟踪目标节点 self.selectionBox = self.world.render.attachNewNode("selectionBox") self.selectionBoxTarget = nodePath # 保存目标节点引用 print(f" 选择框节点创建完成: {self.selectionBox}") - + # 启动选择框更新任务 taskMgr.add(self.updateSelectionBoxTask, "updateSelectionBox") print(" 选择框更新任务已启动") - + # 初始更新选择框 print(" 开始初始化选择框几何体...") self.updateSelectionBoxGeometry() - + print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框") - + except Exception as e: print(f" ✗ 创建选择框失败: {str(e)}") import traceback traceback.print_exc() - + def updateSelectionBoxGeometry(self): """更新选择框的几何形状和位置""" try: if not self.selectionBox or not self.selectionBoxTarget: return - + # 清除现有的几何体 self.selectionBox.removeNode() self.selectionBox = self.world.render.attachNewNode("selectionBox") - + # 获取目标节点在世界坐标系中的边界框(使用正确的API) minPoint = Point3() maxPoint = Point3() if not self.selectionBoxTarget.calcTightBounds(minPoint, maxPoint, self.world.render): return - + # 获取边界框的最小和最大点(世界坐标) print(f"世界边界框: min={minPoint}, max={maxPoint}") - + # 创建线段对象 lines = LineSegs() lines.setThickness(2.0) - + # 定义立方体的8个顶点 vertices = [ (minPoint.x, minPoint.y, minPoint.z), # 0: 前下左 @@ -131,7 +132,7 @@ class SelectionSystem: (maxPoint.x, maxPoint.y, maxPoint.z), # 6: 后上右 (minPoint.x, maxPoint.y, maxPoint.z), # 7: 后上左 ] - + # 定义立方体的边(连接顶点的线段) edges = [ # 底面 @@ -141,272 +142,472 @@ class SelectionSystem: # 垂直边 (0, 4), (1, 5), (2, 6), (3, 7) ] - + # 绘制所有边 for start, end in edges: lines.moveTo(*vertices[start]) lines.drawTo(*vertices[end]) - + # 创建选择框几何体 geomNode = lines.create() self.selectionBox.attachNewNode(geomNode) - + # 设置选择框的颜色为亮橙色 self.selectionBox.setColor(1.0, 0.5, 0.0, 1.0) - + # 设置渲染状态,确保线框总是在最前面显示 state = RenderState.make( DepthTestAttrib.make(DepthTestAttrib.MLess), ColorAttrib.makeFlat((1.0, 0.5, 0.0, 1.0)) ) self.selectionBox.setState(state) - + # 确保选择框不被光照影响 self.selectionBox.setLightOff() - + # 让选择框稍微大一点,避免与模型重叠 self.selectionBox.setScale(1.01) - + except Exception as e: print(f"更新选择框几何体失败: {str(e)}") import traceback traceback.print_exc() - + def updateSelectionBoxTask(self, task): """选择框更新任务""" try: if not self.selectionBox or not self.selectionBoxTarget: return task.done # 结束任务 - + # 检查目标节点是否还存在 if self.selectionBoxTarget.isEmpty(): self.clearSelectionBox() return task.done - + # 获取目标节点在世界坐标系中的当前边界框(使用正确的API) currentMinPoint = Point3() currentMaxPoint = Point3() if not self.selectionBoxTarget.calcTightBounds(currentMinPoint, currentMaxPoint, self.world.render): return task.cont - + # 检查边界框是否发生变化(位置或大小) if (not hasattr(self, '_lastMinPoint') or not hasattr(self, '_lastMaxPoint') or self._lastMinPoint != currentMinPoint or self._lastMaxPoint != currentMaxPoint): - + # 更新选择框几何体 self.updateSelectionBoxGeometry() - + # 保存当前边界框信息 self._lastMinPoint = currentMinPoint self._lastMaxPoint = currentMaxPoint - + return task.cont # 继续任务 - + except Exception as e: print(f"选择框更新任务出错: {str(e)}") return task.done - + def clearSelectionBox(self): """清除选择框""" if self.selectionBox: self.selectionBox.removeNode() self.selectionBox = None - + # 停止选择框更新任务 taskMgr.remove("updateSelectionBox") - + # 清除目标节点引用 self.selectionBoxTarget = None - + print("清除了选择框") - + # ==================== 坐标轴(Gizmo)系统 ==================== - + def createGizmo(self, nodePath): - """为选中的节点创建坐标轴工具""" + """为选中的节点创建坐标轴工具 - 保留箭头版本""" try: print(f" 开始创建坐标轴,目标节点: {nodePath.getName()}") - + # 如果已有坐标轴,先移除 if self.gizmo: - print(" 移除现有坐标轴") self.gizmo.removeNode() self.gizmo = None - + taskMgr.remove("updateGizmo") + if not nodePath: - print(" 目标节点为空,取消创建") return - - # 创建坐标轴主节点 + + # 创建坐标轴主节点 self.gizmo = self.world.render.attachNewNode("gizmo") self.gizmoTarget = nodePath - print(f" 坐标轴主节点创建完成: {self.gizmo}") - - # 获取目标节点在世界坐标系中的边界框(使用正确的API) + + # 设置位置和朝向 minPoint = Point3() maxPoint = Point3() if nodePath.calcTightBounds(minPoint, maxPoint, self.world.render): - # 计算中心点 center = Point3((minPoint.x + maxPoint.x) * 0.5, - (minPoint.y + maxPoint.y) * 0.5, - (minPoint.z + maxPoint.z) * 0.5) - # 将坐标轴放在实体的中心位置 + (minPoint.y + maxPoint.y) * 0.5, + (minPoint.z + maxPoint.z) * 0.5) self.gizmo.setPos(center) - print(f" 坐标轴位置设置为实体中心: {center}") - else: - print(" 目标节点边界框为空,使用默认位置") - - # 【关键修复】:设置坐标轴的朝向以反映父节点的旋转 + parent_node = nodePath.getParent() if parent_node and parent_node != self.world.render: - # 子节点:坐标轴应该和父节点保持相同的朝向 - parent_hpr = parent_node.getHpr() - self.gizmo.setHpr(parent_hpr) - print(f" 子节点坐标轴 - 设置朝向与父节点一致: {parent_hpr}") + self.gizmo.setHpr(parent_node.getHpr()) else: - # 顶级模型:使用世界坐标系朝向 self.gizmo.setHpr(0, 0, 0) - print(f" 顶级模型坐标轴 - 使用世界坐标系朝向") - - # 创建坐标轴的几何体 - print(" 开始创建坐标轴几何体...") + + # 只调用一次几何体创建 self.createGizmoGeometry() - - # 启动坐标轴更新任务 + + # 只调用一次颜色设置 + self.setGizmoAxisColor("x", self.gizmo_colors["x"]) + self.setGizmoAxisColor("y", self.gizmo_colors["y"]) + self.setGizmoAxisColor("z", self.gizmo_colors["z"]) + + # 只启动一次更新任务 taskMgr.add(self.updateGizmoTask, "updateGizmo") - print(" 坐标轴更新任务已启动") - + print(f" ✓ 为节点 {nodePath.getName()} 创建了坐标轴") - + except Exception as e: print(f"创建坐标轴失败: {str(e)}") - import traceback - traceback.print_exc() - def createGizmoGeometry(self): """创建坐标轴的几何体""" + from panda3d.core import Material try: if not self.gizmo: return - + # 创建X轴(红色) x_lines = LineSegs("x_axis") x_lines.setThickness(6.0) x_lines.moveTo(0, 0, 0) x_lines.drawTo(self.axis_length, 0, 0) - # 创建X轴箭头 x_lines.moveTo(self.axis_length - 0.5, -0.2, 0) x_lines.drawTo(self.axis_length, 0, 0) x_lines.drawTo(self.axis_length - 0.5, 0.2, 0) - x_geom = x_lines.create() self.gizmoXAxis = self.gizmo.attachNewNode(x_geom) self.gizmoXAxis.setName("gizmo_x_axis") - self.gizmoXAxis.setLightOff() + #self.gizmoXAxis.setLightOff() # 创建Y轴(绿色) y_lines = LineSegs("y_axis") y_lines.setThickness(6.0) y_lines.moveTo(0, 0, 0) y_lines.drawTo(0, self.axis_length, 0) - # 创建Y轴箭头 y_lines.moveTo(-0.2, self.axis_length - 0.5, 0) y_lines.drawTo(0, self.axis_length, 0) y_lines.drawTo(0.2, self.axis_length - 0.5, 0) - y_geom = y_lines.create() self.gizmoYAxis = self.gizmo.attachNewNode(y_geom) self.gizmoYAxis.setName("gizmo_y_axis") - self.gizmoYAxis.setLightOff() - + #self.gizmoYAxis.setLightOff() + # 创建Z轴(蓝色) z_lines = LineSegs("z_axis") z_lines.setThickness(6.0) z_lines.moveTo(0, 0, 0) z_lines.drawTo(0, 0, self.axis_length) - # 创建Z轴箭头 z_lines.moveTo(-0.2, 0, self.axis_length - 0.5) z_lines.drawTo(0, 0, self.axis_length) z_lines.drawTo(0.2, 0, self.axis_length - 0.5) - z_geom = z_lines.create() self.gizmoZAxis = self.gizmo.attachNewNode(z_geom) self.gizmoZAxis.setName("gizmo_z_axis") - self.gizmoZAxis.setLightOff() - + #self.gizmoZAxis.setLightOff() + # 确保坐标轴不被光照影响 - self.gizmo.setLightOff() - + #self.gizmo.setLightOff() + # 使用最强的渲染设置,确保坐标轴绝对不会被遮挡 self.gizmo.setBin("gui-popup", 0) # 使用最高的GUI渲染层 self.gizmo.setDepthTest(False) # 完全禁用深度测试 self.gizmo.setDepthWrite(False) # 禁用深度写入 self.gizmo.setTwoSided(True) # 双面渲染 - + # 创建强制前景渲染状态 from panda3d.core import RenderModeAttrib, TransparencyAttrib foreground_state = RenderState.make( DepthTestAttrib.make(DepthTestAttrib.MNone), # 完全不进行深度测试 TransparencyAttrib.make(TransparencyAttrib.MAlpha) # 启用透明度混合 ) - self.gizmo.setState(foreground_state) - + #self.gizmo.setState(foreground_state) + # 对每个坐标轴设置独立的最高渲染优先级 self.gizmoXAxis.setBin("gui-popup", 10) self.gizmoXAxis.setDepthTest(False) self.gizmoXAxis.setDepthWrite(False) self.gizmoXAxis.setLightOff() self.gizmoXAxis.setState(foreground_state) - + self.gizmoYAxis.setBin("gui-popup", 20) self.gizmoYAxis.setDepthTest(False) self.gizmoYAxis.setDepthWrite(False) self.gizmoYAxis.setLightOff() self.gizmoYAxis.setState(foreground_state) - + self.gizmoZAxis.setBin("gui-popup", 30) self.gizmoZAxis.setDepthTest(False) self.gizmoZAxis.setDepthWrite(False) self.gizmoZAxis.setLightOff() self.gizmoZAxis.setState(foreground_state) - - # 强制设置各轴的渲染状态,确保颜色可以变化 - # 创建包含颜色和前景渲染的组合状态 - red_state = RenderState.make( - ColorAttrib.makeFlat((1, 0, 0, 1)), - DepthTestAttrib.make(DepthTestAttrib.MNone), - TransparencyAttrib.make(TransparencyAttrib.MAlpha) - ) - green_state = RenderState.make( - ColorAttrib.makeFlat((0, 1, 0, 1)), - DepthTestAttrib.make(DepthTestAttrib.MNone), - TransparencyAttrib.make(TransparencyAttrib.MAlpha) - ) - blue_state = RenderState.make( - ColorAttrib.makeFlat((0, 0, 1, 1)), - DepthTestAttrib.make(DepthTestAttrib.MNone), - TransparencyAttrib.make(TransparencyAttrib.MAlpha) - ) - - self.gizmoXAxis.setState(red_state) - self.gizmoYAxis.setState(green_state) - self.gizmoZAxis.setState(blue_state) - + + + # 初始化高亮状态 self.gizmoHighlightAxis = None - + # 立即设置初始颜色,确保创建时就有正确的颜色 + self.setGizmoAxisColor("z", self.gizmo_colors["z"]) self.setGizmoAxisColor("x", self.gizmo_colors["x"]) self.setGizmoAxisColor("y", self.gizmo_colors["y"]) - self.setGizmoAxisColor("z", self.gizmo_colors["z"]) - + + print(f"✓ 坐标轴几何体创建完成,长度={self.axis_length}") - + + # 为 RenderPipeline 环境设置正确的渲染状态 + self._setupRenderPipelineCompatibleGizmo() + + self._setupEmissiveMaterials() + self._setupGizmoRendering() + except Exception as e: print(f"创建坐标轴几何体失败: {str(e)}") - + + def _setupEmissiveMaterials(self): + try: + from panda3d.core import Material,Vec4 + materials ={ + "x":(Vec4(1,0,0,1),Vec4(2.0,0,0,1)), + "y":(Vec4(0,1,0,1),Vec4(0,2.0,0,1)), + "z":(Vec4(0,0,1,1),Vec4(0,0,2.0,1)) + } + axis_nodes ={ + "x":self.gizmoXAxis, + "y":self.gizmoYAxis, + "z":self.gizmoZAxis + } + for axis,(base_color,emission_color) in materials.items(): + if axis_nodes[axis]: + material=Material(f"gizmo_{axis}_material") + material.setBaseColor(base_color) + material.setEmission(emission_color) + material.setRoughness(1.0) + material.setMetallic(0.0) + axis_nodes[axis].setMaterial(material) + print("自发光材质设置完成") + except Exception as e: + print(f"自发光材质设置失败: {str(e)}") + + def _setupGizmoRendering(self): + """设置坐标轴渲染属性""" + try: + # 设置渲染优先级,确保在最前面显示 + self.gizmo.setBin("gui-popup", 1000) + self.gizmo.setDepthTest(False) + self.gizmo.setDepthWrite(False) + self.gizmo.setLightOff() + + # 为每个轴设置独立的渲染属性 + for i, axis_node in enumerate([self.gizmoXAxis, self.gizmoYAxis, self.gizmoZAxis]): + if axis_node: + axis_node.setBin("gui-popup", 1001 + i) + axis_node.setDepthTest(False) + axis_node.setDepthWrite(False) + axis_node.setLightOff() + + print("✓ 坐标轴渲染设置完成") + + except Exception as e: + print(f"设置坐标轴渲染失败!!!!!!: {str(e)}") + + def _setupRenderPipelineCompatibleGizmo(self): + """为 RenderPipeline 环境设置兼容的坐标轴渲染 - 激进修复版本""" + try: + from panda3d.core import (ShaderAttrib, MaterialAttrib, RenderModeAttrib, + AntialiasAttrib, TransparencyAttrib, CullFaceAttrib, + RescaleNormalAttrib, TextureAttrib) + + # 第一步:完全隔离坐标轴,避免被任何系统处理 + self._isolateGizmoFromRenderPipeline() + + # 第二步:使用最简单的渲染方式 + self._setupMinimalGizmoRendering() + + # 第三步:强制设置每个轴的独立渲染 + self._forceAxisIndependentRendering() + + print("✅ 激进 RenderPipeline 兼容坐标轴设置完成") + + except Exception as e: + print(f"❌ 激进设置失败: {e}") + # 使用最后的备用方案 + self._setupUltimateGizmoFallback() + + def _isolateGizmoFromRenderPipeline(self): + """完全隔离坐标轴,避免被 RenderPipeline 处理""" + try: + # 设置所有可能的隔离标签 + isolation_tags = [ + ("no_shadow", "1"), + ("no_lighting", "1"), + ("no_material", "1"), + ("no_shader", "1"), + ("no_fog", "1"), + ("no_texture", "1"), + ("gui_element", "1"), + ("bypass_rp", "1"), + ("fixed_pipeline", "1"), + ("ignore_all", "1") + ] + + for tag, value in isolation_tags: + self.gizmo.setTag(tag, value) + + # 完全禁用所有高级功能,使用最高优先级 + self.gizmo.setShaderOff(10000) + self.gizmo.setLightOff(10000) + self.gizmo.setFogOff(10000) + self.gizmo.setMaterialOff(10000) + self.gizmo.setTextureOff(10000) + + # 禁用所有可能的渲染特性 + from panda3d.core import RenderModeAttrib, CullFaceAttrib + # self.gizmo.setRenderModeWireframe() + # self.gizmo.setTwoSided(True) + self.gizmo.setRenderMode(RenderModeAttrib.MFilled) + self.gizmo.setTwoSided(True) + + self.gizmo.setColorScale(2.0,2.0,2.0,1.0) + for axis_node in [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]: + if axis_node: + axis_node.setTag("emissive","1") + axis_node.setTag("unlit","1") + axis_node.setColorScale(2.0,2.0,2.0,1.0) + + print(" ✓ 坐标轴完全隔离") + + except Exception as e: + print(f" ❌ 隔离失败: {e}") + + def _setupMinimalGizmoRendering(self): + """设置最简单的渲染方式""" + try: + from panda3d.core import (ShaderAttrib, MaterialAttrib, TextureAttrib, + CullFaceAttrib, RescaleNormalAttrib) + + # 使用最高优先级的 GUI 渲染 bin + self.gizmo.setBin("gui-popup", 10000) + self.gizmo.setDepthTest(False) + self.gizmo.setDepthWrite(False) + + # 创建最简单的渲染状态 + minimal_state = RenderState.make( + ShaderAttrib.makeOff(10000), # 完全禁用着色器 + MaterialAttrib.makeOff(), # 禁用材质 + TextureAttrib.makeOff(), # 禁用纹理 + CullFaceAttrib.make(CullFaceAttrib.MCullNone), # 禁用面剔除 + RescaleNormalAttrib.makeOff() # 禁用法线重缩放 + ) + self.gizmo.setState(minimal_state, 10000) + + print(" ✓ 最简渲染设置完成") + + except Exception as e: + print(f" ❌ 最简渲染设置失败: {e}") + + def _forceAxisIndependentRendering(self): + """强制设置每个轴的独立渲染""" + try: + # 轴配置 + axis_configs = [ + (self.gizmoXAxis, "X轴", (1.0, 0.0, 0.0, 1.0), 0), + (self.gizmoYAxis, "Y轴", (0.0, 1.0, 0.0, 1.0), 0), + (self.gizmoZAxis, "Z轴", (0.0, 0.0, 1.0, 1.0), 0) + ] + + for axis_node, name, color, priority in axis_configs: + if axis_node: + # 每个轴都完全独立设置 + self._setupSingleAxisRendering(axis_node, name, color, 0) + + print(" ✓ 独立轴渲染设置完成") + + except Exception as e: + print(f" ❌ 独立轴渲染设置失败: {e}") + + def _setupSingleAxisRendering(self, axis_node, name, color, priority): + """为单个轴设置完全独立的渲染""" + try: + from panda3d.core import (LVecBase4f, RenderState, ColorAttrib, + TransparencyAttrib, LColor, AntialiasAttrib, + RenderModeAttrib, CullFaceAttrib, AuxBitplaneAttrib, + LightRampAttrib) + + # 转换颜色为LColor并增加亮度 + base_color = LColor(*color) + emissive_color = LColor(base_color[0], base_color[1], base_color[2], 1.0) + + # 完全禁用所有高级渲染功能 + axis_node.clearShader() + axis_node.clearTexture() + axis_node.clearMaterial() + axis_node.setLightOff() + axis_node.setFogOff() + axis_node.setAttrib(RenderModeAttrib.make(RenderModeAttrib.MWireframe, 6.0)) + axis_node.setAttrib(AntialiasAttrib.make(AntialiasAttrib.MLine)) + axis_node.setBin("gui-popup", 0) + axis_node.setDepthTest(False) + axis_node.setDepthWrite(False) + axis_node.setTwoSided(True) + + # 强制设置自发光颜色 + axis_node.setColor(*color) + axis_node.setColorScale(1.0, 1.0, 1.0, 1.0) # 增加整体亮度 + + except Exception as e: + print(" ❌ {} 轴渲染设置失败: {}".format(name, str(e))) + raise e + + def _setupUltimateGizmoFallback(self): + """最后的备用方案 - 最简单的渲染""" + try: + print("🚨 使用最后备用方案...") + + # 最简单的设置 + self.gizmo.setLightOff() + self.gizmo.setFogOff() + self.gizmo.setBin("gui-popup", 20000) + self.gizmo.setDepthTest(False) + self.gizmo.setDepthWrite(False) + + # 直接设置颜色,不使用复杂的渲染状态 + if self.gizmoXAxis: + self.gizmoXAxis.setColor(1, 0, 0, 1) + self.gizmoXAxis.setLightOff() + self.gizmoXAxis.setBin("gui-popup", 20001) + self.gizmoXAxis.setDepthTest(False) + + if self.gizmoYAxis: + self.gizmoYAxis.setColor(0, 1, 0, 1) + self.gizmoYAxis.setLightOff() + self.gizmoYAxis.setBin("gui-popup", 20002) + self.gizmoYAxis.setDepthTest(False) + + if self.gizmoZAxis: + self.gizmoZAxis.setColor(0, 0, 1, 1) + self.gizmoZAxis.setLightOff() + self.gizmoZAxis.setBin("gui-popup", 20003) + self.gizmoZAxis.setDepthTest(False) + + print("✅ 最后备用方案设置完成") + + except Exception as e: + print(f"❌ 最后备用方案也失败: {e}") + def updateGizmoTask(self, task): """坐标轴更新任务""" try: @@ -446,20 +647,20 @@ class SelectionSystem: self.gizmo.setHpr(0, 0, 0) return task.cont - + except Exception as e: print(f"坐标轴更新任务出错: {str(e)}") return task.done - + def clearGizmo(self): """清除坐标轴""" if self.gizmo: self.gizmo.removeNode() self.gizmo = None - + # 停止坐标轴更新任务 taskMgr.remove("updateGizmo") - + # 清除坐标轴相关引用 self.gizmoTarget = None self.gizmoXAxis = None @@ -470,97 +671,174 @@ class SelectionSystem: self.dragStartMousePos = None self.gizmoTargetStartPos = None self.gizmoStartPos = None - + print("清除了坐标轴") - + def setGizmoAxisColor(self, axis, color): - """设置坐标轴颜色 - 使用前景渲染状态确保不被遮挡""" + """设置坐标轴颜色 - RenderPipeline 兼容版本""" try: - # 创建包含颜色和前景渲染的组合状态 - from panda3d.core import TransparencyAttrib - color_state = RenderState.make( - ColorAttrib.makeFlat(color), - DepthTestAttrib.make(DepthTestAttrib.MNone), - TransparencyAttrib.make(TransparencyAttrib.MAlpha) - ) - - if axis == "x" and self.gizmoXAxis: - self.gizmoXAxis.setState(color_state) - self.gizmoXAxis.setColor(*color) - self.gizmoXAxis.setColorScale(*color) - elif axis == "y" and self.gizmoYAxis: - self.gizmoYAxis.setState(color_state) - self.gizmoYAxis.setColor(*color) - self.gizmoYAxis.setColorScale(*color) - elif axis == "z" and self.gizmoZAxis: - self.gizmoZAxis.setState(color_state) - self.gizmoZAxis.setColor(*color) - self.gizmoZAxis.setColorScale(*color) + from panda3d.core import AntialiasAttrib,TransparencyAttrib + + axis_nodes = { + "x": self.gizmoXAxis, + "y": self.gizmoYAxis, + "z": self.gizmoZAxis + } + + if axis in axis_nodes and axis_nodes[axis]: + axis_node = axis_nodes[axis] + + axis_node.setColor(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3]) + axis_node.setColorScale(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3]) + axis_node.setShaderOff(10000) + axis_node.setLightOff(10000) + axis_node.setMaterialOff(10000) + axis_node.setTextureOff(1000) + axis_node.setFogOff(10000) + except Exception as e: print(f"设置坐标轴颜色失败: {str(e)}") - + # 回退到简单的颜色设置 + try: + if axis in axis_nodes and axis_nodes[axis]: + axis_nodes[axis].setColor(*color) + except: + pass + + # ==================== 鼠标交互处理 ==================== + + def _onGizmoMouseEnter(self, axis): + """鼠标进入坐标轴时的处理 - RenderPipeline 兼容版本""" + try: + # 黄色高亮,增加透明度以确保在 RenderPipeline 下可见 + highlight_color = (1.0, 1.0, 0.0, 1) + self.setGizmoAxisColor(axis, highlight_color) + + # 额外的视觉反馈 + axis_nodes = { + "x": self.gizmoXAxis, + "y": self.gizmoYAxis, + "z": self.gizmoZAxis + } + + if axis in axis_nodes and axis_nodes[axis]: + # 稍微放大以增强视觉效果 + axis_nodes[axis].setScale(1.1) + + except Exception as e: + print(f"鼠标进入坐标轴处理失败: {e}") + + def _onGizmoMouseLeave(self, axis): + """鼠标离开坐标轴时的处理 - RenderPipeline 兼容版本""" + try: + # 恢复原始颜色 + original_colors = { + "x": (1.0, 0.0, 0.0, 1.0), # 红色 + "y": (0.0, 1.0, 0.0, 1.0), # 绿色 + "z": (0.0, 0.0, 1.0, 1.0) # 蓝色 + } + + if axis in original_colors: + self.setGizmoAxisColor(axis, original_colors[axis]) + + # 恢复原始大小 + axis_nodes = { + "x": self.gizmoXAxis, + "y": self.gizmoYAxis, + "z": self.gizmoZAxis + } + + if axis in axis_nodes and axis_nodes[axis]: + axis_nodes[axis].setScale(1.0) + + except Exception as e: + print(f"鼠标离开坐标轴处理失败: {e}") + + def setupGizmoMouseEvents(self): + """设置坐标轴的鼠标事件""" + try: + from direct.showbase.DirectObject import DirectObject + + # 为每个轴设置鼠标事件 + axis_nodes = { + "x": self.gizmoXAxis, + "y": self.gizmoYAxis, + "z": self.gizmoZAxis + } + + for axis_name, axis_node in axis_nodes.items(): + if axis_node: + # 设置碰撞检测标签 + axis_node.setTag("gizmo_axis", axis_name) + axis_node.setTag("pickable", "1") + + print("✓ 坐标轴鼠标事件设置完成") + + except Exception as e: + print(f"设置坐标轴鼠标事件失败: {e}") + # ==================== 射线检测和碰撞检测 ==================== - + def checkGizmoClick(self, mouseX, mouseY): """使用屏幕空间检测是否点击了坐标轴""" if not self.gizmo or not self.gizmoTarget: print("坐标轴点击检测:坐标轴或目标不存在") return None - + # 基本参数验证 if not isinstance(mouseX, (int, float)) or not isinstance(mouseY, (int, float)): print(f"坐标轴点击检测:无效的鼠标坐标 ({mouseX}, {mouseY})") return None - + try: print(f"\n=== 坐标轴点击检测 ===") print(f"鼠标位置: ({mouseX}, {mouseY})") - + # 获取坐标轴中心的世界坐标 gizmo_world_pos = self.gizmo.getPos(self.world.render) print(f"坐标轴世界位置: {gizmo_world_pos}") - + # 计算各轴端点的世界坐标 x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0) - y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) + y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length) - + # 使用Panda3D的内置投影方法 def worldToScreen(world_pos): """将世界坐标转换为屏幕坐标""" # 将世界坐标转换为相机空间 cam_space_pos = self.world.cam.getRelativePoint(self.world.render, world_pos) - + # 检查是否在相机前方 if cam_space_pos.getY() <= 0: return None - + # 使用相机的镜头进行投影 screen_pos = Point2() if self.world.cam.node().getLens().project(cam_space_pos, screen_pos): # 获取准确的窗口尺寸 win_width, win_height = self.world.getWindowSize() - + # 转换为窗口像素坐标 win_x = (screen_pos.getX() + 1.0) * 0.5 * win_width win_y = (1.0 - screen_pos.getY()) * 0.5 * win_height return (win_x, win_y) return None - + # 投影各个关键点 center_screen = worldToScreen(gizmo_world_pos) x_screen = worldToScreen(x_end) y_screen = worldToScreen(y_end) z_screen = worldToScreen(z_end) - + # 如果无法获得屏幕坐标,使用备用方法 if not center_screen: print("使用备用检测方法...") return self.checkGizmoClickFallback(mouseX, mouseY) - + # 计算点击阈值 click_threshold = 30 # 增大检测范围 - + # 检测各个轴,对于端点在屏幕外的轴提供回退方案 def getClickDetectionPoint(axis_name, original_screen_pos): if original_screen_pos: @@ -575,13 +853,13 @@ class SelectionSystem: else: return None return worldToScreen(half_end) - + axes_data = [ ("x", getClickDetectionPoint("x", x_screen), "X轴"), - ("y", getClickDetectionPoint("y", y_screen), "Y轴"), + ("y", getClickDetectionPoint("y", y_screen), "Y轴"), ("z", getClickDetectionPoint("z", z_screen), "Z轴") ] - + for axis_name, axis_screen, axis_label in axes_data: if axis_screen: # 计算鼠标到轴线的距离 @@ -589,48 +867,48 @@ class SelectionSystem: (mouseX, mouseY), center_screen, axis_screen ) print(f"{axis_label}距离: {distance:.2f}") - + if distance < click_threshold: print(f"✓ 点击了{axis_label}") return axis_name - + print("× 没有点击任何轴") return None - + except Exception as e: print(f"坐标轴点击检测失败: {str(e)}") import traceback traceback.print_exc() return None - + def checkGizmoClickFallback(self, mouseX, mouseY): """备用检测方法:使用固定的屏幕区域""" print("使用备用点击检测...") - + # 获取准确的窗口尺寸 win_width, win_height = self.world.getWindowSize() - + # 获取窗口中心作为参考点 center_x = win_width // 2 center_y = win_height // 2 - + # 定义相对于中心的轴区域(简化假设坐标轴在屏幕中心附近) axis_length_pixels = 100 # 假设轴长度在屏幕上约100像素 - + # X轴:从中心向右 x_start = (center_x, center_y) x_end = (center_x + axis_length_pixels, center_y) - + # Y轴:从中心向上(注意Y轴方向) y_start = (center_x, center_y) y_end = (center_x, center_y - axis_length_pixels) - + # Z轴:从中心向右上45度 z_start = (center_x, center_y) z_end = (center_x + axis_length_pixels * 0.7, center_y - axis_length_pixels * 0.7) - + threshold = 25 - + # 检测各轴 if self.distanceToLine((mouseX, mouseY), x_start, x_end) < threshold: print("✓ 备用方法检测到X轴") @@ -641,71 +919,96 @@ class SelectionSystem: elif self.distanceToLine((mouseX, mouseY), z_start, z_end) < threshold: print("✓ 备用方法检测到Z轴") return "z" - + print("× 备用方法也没有检测到") return None - + def distanceToLine(self, point, line_start, line_end): """计算点到线段的距离""" try: px, py = point x1, y1 = line_start x2, y2 = line_end - + # 计算线段长度 line_length = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 if line_length == 0: return ((px - x1) ** 2 + (py - y1) ** 2) ** 0.5 - + # 计算点到线的距离 t = max(0, min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (line_length ** 2))) projection_x = x1 + t * (x2 - x1) projection_y = y1 + t * (y2 - y1) - + distance = ((px - projection_x) ** 2 + (py - projection_y) ** 2) ** 0.5 return distance - + except Exception as e: print(f"距离计算错误: {e}") return float('inf') - + # ==================== 高亮和交互 ==================== - + def updateGizmoHighlight(self, mouseX, mouseY): """更新坐标轴高亮状态""" if not self.gizmo or self.isDraggingGizmo: return - + + import time + current_time = time.time() + if not hasattr(self,'_last_highlight_time'): + self._last_highlight_time = 0 + + if current_time - self._last_highlight_time<0.05: + return + self._last_highlight_time = current_time + + hoveredAxis = self._detectHoveredAxis(mouseX, mouseY) + + if not hasattr(self,'_hover_stability_counter'): + self._hover_stability_counter = {} + self._last_detected_axis = None + + if hoveredAxis !=self._last_detected_axis: + self._hover_stability_counter[hoveredAxis]=1 + self._last_detected_axis = hoveredAxis + else: + self._hover_stability_counter[hoveredAxis] = self._hover_stability_counter.get(hoveredAxis,0)+1 + + if self._hover_stability_counter.get(hoveredAxis,0)>=2: + if hoveredAxis != self.gizmoHighlightAxis: + self._updateAxisHighlight(hoveredAxis) + # 检测鼠标悬停的轴(使用相同的检测逻辑但不输出调试信息) hoveredAxis = None - + try: # 获取坐标轴中心的世界坐标 gizmo_world_pos = self.gizmo.getPos(self.world.render) - + # 计算各轴端点的世界坐标 x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0) - y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) + y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length) - + # 将3D坐标投影到屏幕坐标 def worldToScreen(worldPos): try: # 转换为相机坐标系 camPos = self.world.cam.getRelativePoint(self.world.render, worldPos) - + # 检查点是否在相机前方 if camPos.getY() <= 0: return None - + # 使用相机lens进行投影 screenPos = Point2() lens = self.world.cam.node().getLens() - + if lens.project(camPos, screenPos): # 获取准确的窗口尺寸 winWidth, winHeight = self.world.getWindowSize() - + # 转换为像素坐标 winX = (screenPos.x + 1) * 0.5 * winWidth winY = (1 - screenPos.y) * 0.5 * winHeight @@ -713,38 +1016,38 @@ class SelectionSystem: return None except: return None - + # 获取各坐标轴的屏幕投影 gizmo_screen = worldToScreen(gizmo_world_pos) x_screen = worldToScreen(x_end) y_screen = worldToScreen(y_end) z_screen = worldToScreen(z_end) - + # 只要坐标轴中心在屏幕内,就进行检测 if gizmo_screen: click_threshold = 25 - + def isNearLine(mousePos, start, end, threshold): import math A = mousePos[1] - start[1] - B = start[0] - mousePos[0] + B = start[0] - mousePos[0] C = (end[1] - start[1]) * mousePos[0] + (start[0] - end[0]) * mousePos[1] + end[0] * start[1] - start[0] * end[1] - + length = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2) if length == 0: return False - + distance = abs(C) / length - t = ((mousePos[0] - start[0]) * (end[0] - start[0]) + + t = ((mousePos[0] - start[0]) * (end[0] - start[0]) + (mousePos[1] - start[1]) * (end[1] - start[1])) / (length * length) - + return distance < threshold and 0 <= t <= 1 - + mouse_pos = (mouseX, mouseY) - + # 分别检测每个轴,为在屏幕外的轴端点提供替代方案 # 按优先级检测轴(Z > X > Y) - + # 对于轴端点在屏幕外的情况,使用较短的轴段进行检测 def getAxisScreenPoint(axis_name, axis_screen_end): if axis_screen_end: @@ -757,36 +1060,54 @@ class SelectionSystem: elif axis_name == "z": half_end = gizmo_world_pos + Vec3(0, 0, self.axis_length * 0.5) return worldToScreen(half_end) - + # 获取有效的检测点(优先使用完整轴,备用使用半轴) z_detect_point = getAxisScreenPoint("z", z_screen) x_detect_point = getAxisScreenPoint("x", x_screen) y_detect_point = getAxisScreenPoint("y", y_screen) - + if z_detect_point and isNearLine(mouse_pos, gizmo_screen, z_detect_point, click_threshold): hoveredAxis = "z" elif x_detect_point and isNearLine(mouse_pos, gizmo_screen, x_detect_point, click_threshold): hoveredAxis = "x" elif y_detect_point and isNearLine(mouse_pos, gizmo_screen, y_detect_point, click_threshold): hoveredAxis = "y" - + except Exception as e: pass # 静默处理错误,避免频繁输出 - + # 如果高亮状态发生变化 if hoveredAxis != self.gizmoHighlightAxis: # 恢复之前高亮的轴 if self.gizmoHighlightAxis: self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis]) - + # 高亮新的轴 if hoveredAxis: self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis]) - + self.gizmoHighlightAxis = hoveredAxis - + + def _detectHoveredAxis(self, mouseX, mouseY): + """检测鼠标悬停的轴 - 提取为独立方法""" + # 将原来 updateGizmoHighlight 中的检测逻辑移到这里 + # ... 你原来的检测代码 ... + pass + + def _updateAxisHighlight(self, hoveredAxis): + """更新轴高亮状态 - 确保原子性操作""" + # 恢复之前高亮的轴 + if self.gizmoHighlightAxis: + self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis]) + + # 高亮新的轴 + if hoveredAxis: + self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis]) + + self.gizmoHighlightAxis = hoveredAxis + # ==================== 拖拽变换 ==================== - + def startGizmoDrag(self, axis, mouseX, mouseY): """开始坐标轴拖拽""" try: @@ -797,22 +1118,22 @@ class SelectionSystem: if not self.gizmo: print("开始拖拽失败: 没有坐标轴") return - + self.isDraggingGizmo = True self.dragGizmoAxis = axis self.dragStartMousePos = (mouseX, mouseY) - + # 保存开始拖拽时目标节点的位置和坐标轴的位置 self.gizmoTargetStartPos = self.gizmoTarget.getPos() self.gizmoStartPos = self.gizmo.getPos(self.world.render) # 坐标轴的世界位置 - + print(f"开始拖拽 {axis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})") - + except Exception as e: print(f"开始坐标轴拖拽失败: {str(e)}") import traceback traceback.print_exc() - + def updateGizmoDrag(self, mouseX, mouseY): """更新坐标轴拖拽 - 使用正确的坐标系变换,支持旋转后的子节点拖拽""" try: @@ -832,18 +1153,18 @@ class SelectionSystem: if not hasattr(self, 'gizmoStartPos') or not self.gizmoStartPos: print("拖拽更新失败: 没有坐标轴起始位置") return - + # 计算鼠标移动距离(屏幕像素) mouseDeltaX = mouseX - self.dragStartMousePos[0] mouseDeltaY = mouseY - self.dragStartMousePos[1] - + # 使用坐标轴的实际位置而不是目标节点位置来计算屏幕投影 gizmo_world_pos = self.gizmoStartPos - + # 【关键修复】:获取正确的轴向量,考虑父节点的旋转 # 检查目标节点是否有父节点 parent_node = self.gizmoTarget.getParent() - + # 确定轴向量的变换上下文 if parent_node and parent_node != self.world.render: # 子节点:使用父节点的局部坐标系 @@ -853,7 +1174,7 @@ class SelectionSystem: # 顶级模型:使用世界坐标系 print(f"顶级模型拖拽 - 使用世界坐标系") transform_context = self.world.render - + # 计算轴向量在正确坐标系中的方向 if self.dragGizmoAxis == "x": # 在变换上下文中的X轴方向 @@ -867,7 +1188,7 @@ class SelectionSystem: else: print(f"拖拽更新失败: 未知轴类型 {self.dragGizmoAxis}") return - + # 将局部轴向量转换到世界坐标系(用于屏幕投影) if transform_context != self.world.render: # 获取变换矩阵并应用到轴向量上 @@ -880,25 +1201,25 @@ class SelectionSystem: # 顶级节点,直接使用世界轴向量 world_axis_vector = local_axis_vector print(f"世界轴向量: {world_axis_vector}") - + # 计算轴的端点位置(用于屏幕投影) axis_end = gizmo_world_pos + world_axis_vector - + # 投影到屏幕空间 def worldToScreen(worldPos): try: # 先转换为相机坐标系 camPos = self.world.cam.getRelativePoint(self.world.render, worldPos) - + # 检查是否在相机前方 if camPos.getY() <= 0: return None - + screenPos = Point2() if self.world.cam.node().getLens().project(camPos, screenPos): # 获取准确的窗口尺寸 winWidth, winHeight = self.world.getWindowSize() - + winX = (screenPos.x + 1) * 0.5 * winWidth winY = (1 - screenPos.y) * 0.5 * winHeight return (winX, winY) @@ -906,23 +1227,23 @@ class SelectionSystem: except Exception as e: print(f"世界坐标转屏幕坐标失败: {e}") return None - + gizmo_screen = worldToScreen(gizmo_world_pos) axis_screen = worldToScreen(axis_end) - + if not gizmo_screen: print("拖拽更新失败: 坐标轴中心不在屏幕内") return if not axis_screen: print("拖拽更新失败: 坐标轴端点不在屏幕内") return - + # 计算轴在屏幕空间的方向向量 screen_axis_dir = ( axis_screen[0] - gizmo_screen[0], axis_screen[1] - gizmo_screen[1] ) - + # 归一化屏幕轴方向 import math length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2) @@ -931,33 +1252,33 @@ class SelectionSystem: else: print("拖拽更新失败: 屏幕轴方向长度为0") return - + # 将鼠标移动投影到轴方向上 - projected_distance = (mouseDeltaX * screen_axis_dir[0] + + projected_distance = (mouseDeltaX * screen_axis_dir[0] + mouseDeltaY * screen_axis_dir[1]) - + # 计算动态比例因子,基于相机距离和视野角度 cam_pos = self.world.cam.getPos() distance_to_object = (cam_pos - gizmo_world_pos).length() - + # 获取相机的视野角度 fov = self.world.cam.node().getLens().getFov()[0] # 水平视野角度 fov_radians = math.radians(fov) - + # 获取窗口尺寸 winWidth, winHeight = self.world.getWindowSize() - + # 计算一个像素在世界坐标系中的大小(在目标物体的距离处) # 使用透视投影公式:world_size = screen_size * distance * tan(fov/2) / (screen_width/2) pixel_to_world_ratio = distance_to_object * math.tan(fov_radians / 2) / (winWidth / 2) - + # 使用动态比例因子 scale_factor = pixel_to_world_ratio * 0.5 # 0.5是调整因子,可以根据需要调整 - + # 【关键修复】:在正确的坐标系中计算移动向量 # 计算移动距离(标量) movement_distance = projected_distance * scale_factor - + # 在正确的坐标系中计算移动向量 if transform_context != self.world.render: # 子节点:在父节点的局部坐标系中移动 @@ -967,7 +1288,7 @@ class SelectionSystem: movement_local = Vec3(0, movement_distance, 0) elif self.dragGizmoAxis == "z": movement_local = Vec3(0, 0, movement_distance) - + # 将局部移动向量转换到父节点的坐标系中 # 由于我们要应用到目标节点上,而目标节点相对于父节点,我们直接使用局部移动 movement = movement_local @@ -981,15 +1302,15 @@ class SelectionSystem: elif self.dragGizmoAxis == "z": movement = Vec3(0, 0, movement_distance) print(f"顶级模型移动向量(世界): {movement}") - + # 应用移动到目标节点 newPos = self.gizmoTargetStartPos + movement self.gizmoTarget.setPos(newPos) - + # 每次拖拽都输出调试信息(但限制频率) if not hasattr(self, '_last_drag_debug_time'): self._last_drag_debug_time = 0 - + import time current_time = time.time() if current_time - self._last_drag_debug_time > 0.1: # 每0.1秒最多输出一次 @@ -1004,36 +1325,36 @@ class SelectionSystem: else: self.gizmoTarget.setPos(newPos) self.gizmo.setPos(newPos) - + except Exception as e: print(f"更新坐标轴拖拽失败: {str(e)}") import traceback traceback.print_exc() - + def stopGizmoDrag(self): """停止坐标轴拖拽""" print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}") - + self.isDraggingGizmo = False self.dragGizmoAxis = None self.dragStartMousePos = None # 清理拖拽状态,下次拖拽开始时重新设置 self.gizmoTargetStartPos = None self.gizmoStartPos = None - + # ==================== 选择管理 ==================== - + def updateSelection(self, nodePath): """更新选择状态""" print(f"\n=== 更新选择状态 ===") print(f"新选择的节点: {nodePath.getName() if nodePath else 'None'}") - + self.selectedNode = nodePath # 添加兼容性属性 self.selectedObject = nodePath if nodePath: print(f"开始为节点 {nodePath.getName()} 创建选择框和坐标轴...") - + # 创建选择框 print("创建选择框...") self.createSelectionBox(nodePath) @@ -1041,7 +1362,7 @@ class SelectionSystem: print(f"✓ 选择框创建成功: {self.selectionBox.getName()}") else: print("× 选择框创建失败") - + # 创建坐标轴 print("创建坐标轴...") self.createGizmo(nodePath) @@ -1049,20 +1370,20 @@ class SelectionSystem: print(f"✓ 坐标轴创建成功: {self.gizmo.getName()}") else: print("× 坐标轴创建失败") - + print(f"✓ 选中了节点: {nodePath.getName()}") else: print("清除选择...") self.clearSelectionBox() self.clearGizmo() print("✓ 取消选择") - + print("=== 选择状态更新完成 ===\n") - + def getSelectedNode(self): """获取当前选中的节点""" return self.selectedNode - + def hasSelection(self): """检查是否有选中的节点""" - return self.selectedNode is not None \ No newline at end of file + return self.selectedNode is not None diff --git a/core/world.py b/core/world.py index 969e2d21..9fbef972 100644 --- a/core/world.py +++ b/core/world.py @@ -37,8 +37,207 @@ class CoreWorld(Panda3DWorld): self._setupLighting() self._setupGround() self._loadFont() + #self.load_and_play_fbx_model() - print("✓ 核心世界初始化完成") + def load_and_play_fbx_model(self): + """加载 glTF 模型并播放动画""" + try: + from direct.actor.Actor import Actor + + # 使用 Actor 类加载 glTF 模型 + self.model = Actor("/home/tiger/Women.glb") + print("模型加载成功!") + self.model.reparentTo(self.render) + self.model.setPos(0, 10, 0) + self.model.setScale(10) + + # 列出所有可用动画 + anims = self.model.getAnimNames() + print("可用动画:", anims) + + # 播放特定动画 + if anims: + self.model.loop('Armature|mixamo.com|Layer0') + + + except Exception as e: + print(f"模型加载失败: {e}") + return None + + def diagnose_fbx_loading(self, fbx_path): + """诊断FBX加载状态""" + print("=== FBX加载诊断 ===") + + # 检查文件存在 + import os + if not os.path.exists(fbx_path): + print("❌ FBX文件不存在") + return None + + try: + # 尝试加载 + actor = Actor(fbx_path) + + # 检查动画 + anims = actor.getAnimNames() + print(f"✓ 找到 {len(anims)} 个动画: {anims}") + + # 检查PartBundle + bundles = actor.getPartBundles() + print(f"✓ 找到 {len(bundles)} 个PartBundle") + + # 测试动画控制器 + if anims: + control = actor.getAnimControl(anims[0]) + if control: + print(f"✓ 动画控制器创建成功: {anims[0]}") + return actor + else: + print(f"❌ 动画控制器创建失败: {anims[0]}") + + return actor + + except Exception as e: + + print(f"❌ FBX加载异常: {e}") + + return None + + + def diagnose_actor_animation(self,model_path, anim_path, anim_name="walk"): + print("=== 开始诊断 Actor 动画问题 ===") + import os + from direct.actor.Actor import Actor + from panda3d.core import Loader + from panda3d.core import Filename + + # 检查文件存在性 + print(f"1. 文件检查:") + print(f" 模型文件存在: {os.path.exists(model_path)}") + print(f" 动画文件存在: {os.path.exists(anim_path)}") + + if not os.path.exists(model_path): + print("❌ 模型文件不存在,请检查路径") + return None + if not os.path.exists(anim_path): + print("❌ 动画文件不存在,请检查路径") + return None + # 2. 检查模型结构 + print(f"2. 模型结构检查:") + try: + # 先用普通loader加载检查结构 + temp_model = self.loader.loadModel(model_path) + if temp_model is None: + print("❌ 无法加载模型文件") + return None + + # 检查Character节点 + bundleNP = temp_model.find("**/+Character") + if bundleNP.isEmpty(): + print("❌ 模型不包含Character节点 - 这是动画模型必需的") + print(" 建议: 确保模型文件是正确的角色模型,包含骨骼结构") + return None + else: + print("✓ 模型包含Character节点") + + temp_model.removeNode() # 清理临时模型 + except Exception as e: + print(f"❌ 模型加载失败: {e}") + return None + # 3. 检查动画文件结构 + print(f"3. 动画文件检查:") + try: + temp_anim = self.loader.loadModel(anim_path) + if temp_anim is None: + print("❌ 无法加载动画文件") + return None + + # 检查AnimBundleNode + animBundleNP = temp_anim.find('**/+AnimBundleNode') + if animBundleNP.isEmpty(): + print("❌ 动画文件不包含AnimBundleNode") + print(" 建议: 确保动画文件是正确导出的动画数据") + return None + else: + print("✓ 动画文件包含AnimBundleNode") + + temp_anim.removeNode() # 清理临时动画 + except Exception as e: + print(f"❌ 动画文件加载失败: {e}") + return None + # 4. 尝试创建Actor + print(f"4. Actor创建测试:") + + # 方法1: 构造函数方式 + try: + print(" 尝试方法1: 构造函数加载") + actor = Actor(model_path, {anim_name: anim_path}) + + print(f" 动画列表: {actor.getAnimNames()}") + + # 强制同步绑定 + print(" 尝试强制绑定动画...") + actor.bindAnim(anim_name, allowAsyncBind=False) + + control = actor.getAnimControl(anim_name) + if control is not None: + print("✓ 方法1成功 - 动画控制器创建成功") + return actor + else: + print("❌ 方法1失败 - 动画控制器为None") + + except Exception as e: + print(f"❌ 方法1异常: {e}") + + # 方法2: 分步加载 + try: + print(" 尝试方法2: 分步加载") + actor = Actor() + actor.loadModel(model_path) + actor.loadAnims({anim_name: anim_path}) + + # 强制绑定 + actor.bindAnim(anim_name, allowAsyncBind=False) + control = actor.getAnimControl(anim_name) + + if control is not None: + print("✓ 方法2成功 - 动画控制器创建成功") + return actor + else: + print("❌ 方法2失败 - 动画控制器为None") + + except Exception as e: + print(f"❌ 方法2异常: {e}") + # 5. 深度诊断 + print(f"5. 深度诊断:") + try: + actor = Actor() + actor.loadModel(model_path) + + # 检查PartBundle + bundles = actor.getPartBundles() + print(f" PartBundle数量: {len(bundles)}") + + if len(bundles) == 0: + print("❌ 没有找到PartBundle - 模型可能不是正确的角色模型") + return None + + # 检查动画绑定过程 + print(" 尝试手动绑定动画...") + actor.loadAnims({anim_name: anim_path}) + + # 获取详细的绑定信息 + actor_info = actor.getActorInfo() + print(f" Actor信息: {actor_info}") + + return None + + except Exception as e: + print(f"❌ 深度诊断异常: {e}") + return None + + print("❌ 所有方法都失败了") + return None def _setYCModel(self): model = self.loader.loadModel("/home/tiger/文档/Tzjyc_GLTF/tzjyc.gltf") @@ -48,19 +247,8 @@ class CoreWorld(Panda3DWorld): model.setHpr(0, 90, 0) - def _setArm1(self): - model1 = self.loader.loadModel("/home/tiger/下载/JQB1.fbx") - model2 = self.loader.loadModel("/home/tiger/下载/JQB2.fbx") - model1.reparentTo(self.render) - model1.setPos(0, 5, 0) - model1.setHpr(0, 90, 0) - model1.setScale(0.01) - model2.reparentTo(self.render) - model2.setPos(0, -5, 0) - model2.setHpr(0, 90, 0) - model2.setScale(0.01) - def _setupCamera(self): + """设置相机位置和朝向""" self.cam.setPos(0, -50, 20) self.cam.lookAt(0, 0, 0) @@ -163,7 +351,7 @@ class CoreWorld(Panda3DWorld): if hasattr(self, 'win') and self.win: width = self.win.getXSize() height = self.win.getYSize() - print(f"从Panda3D窗口获取尺寸: {width} x {height}") + print(f"从Panda3D窗口获取尺寸!!!!!!!!!!!!!!!!!!!!!: {width} x {height}") return width, height # 最后的默认值 diff --git a/main.py b/main.py index 31226662..b0235f8f 100644 --- a/main.py +++ b/main.py @@ -369,6 +369,9 @@ class MyWorld(CoreWorld): def updatePropertyPanel(self, item): """更新属性面板显示 - 代理到property_panel""" return self.property_panel.updatePropertyPanel(item) + + def addAnimationPanel(self,originmodel,filepath): + return self.property_panel._addAnimationPanel(originmodel,filepath) def updateGUIPropertyPanel(self, gui_element): """更新GUI元素属性面板 - 代理到property_panel""" diff --git a/pandatool/CMakeLists.txt b/pandatool/CMakeLists.txt new file mode 100644 index 00000000..8db045f4 --- /dev/null +++ b/pandatool/CMakeLists.txt @@ -0,0 +1,53 @@ +if(NOT BUILD_PANDA) + message(FATAL_ERROR "Cannot build pandatool without panda! Please enable the BUILD_PANDA option.") +endif() + +# Include pandatool source directories +add_subdirectory(src/assimp) +add_subdirectory(src/bam) +add_subdirectory(src/converter) +add_subdirectory(src/daeegg) +add_subdirectory(src/daeprogs) +add_subdirectory(src/deploy-stub) +add_subdirectory(src/dxf) +add_subdirectory(src/dxfegg) +add_subdirectory(src/dxfprogs) +add_subdirectory(src/eggbase) +add_subdirectory(src/eggcharbase) +add_subdirectory(src/eggprogs) +add_subdirectory(src/egg-mkfont) +add_subdirectory(src/egg-optchar) +add_subdirectory(src/egg-palettize) +add_subdirectory(src/egg-qtess) +add_subdirectory(src/flt) +add_subdirectory(src/fltegg) +add_subdirectory(src/fltprogs) +add_subdirectory(src/gtk-stats) +add_subdirectory(src/imagebase) +add_subdirectory(src/imageprogs) +add_subdirectory(src/lwo) +add_subdirectory(src/lwoegg) +add_subdirectory(src/lwoprogs) +add_subdirectory(src/mac-stats) +add_subdirectory(src/miscprogs) +add_subdirectory(src/objegg) +add_subdirectory(src/objprogs) +add_subdirectory(src/palettizer) +add_subdirectory(src/pandatoolbase) +add_subdirectory(src/pfmprogs) +add_subdirectory(src/progbase) +add_subdirectory(src/pstatserver) +add_subdirectory(src/ptloader) +add_subdirectory(src/text-stats) +add_subdirectory(src/vrml) +add_subdirectory(src/vrmlegg) +add_subdirectory(src/vrmlprogs) +add_subdirectory(src/win-stats) +add_subdirectory(src/xfile) +add_subdirectory(src/xfileegg) +add_subdirectory(src/xfileprogs) + +if(BUILD_TOOLS) + export_targets(Tools) +endif() +export_targets(ToolsDevel NAMESPACE "Panda3D::Tools::" COMPONENT ToolsDevel) diff --git a/pandatool/src/assimp/CMakeLists.txt b/pandatool/src/assimp/CMakeLists.txt new file mode 100644 index 00000000..5b88e0ef --- /dev/null +++ b/pandatool/src/assimp/CMakeLists.txt @@ -0,0 +1,39 @@ +if(NOT HAVE_ASSIMP) + return() +endif() + +set(P3ASSIMP_HEADERS + assimpLoader.h assimpLoader.I + config_assimp.h + loaderFileTypeAssimp.h + pandaIOStream.h + pandaIOSystem.h + pandaLogger.h +) + +set(P3ASSIMP_SOURCES + assimpLoader.cxx + config_assimp.cxx + loaderFileTypeAssimp.cxx + pandaIOStream.cxx + pandaIOSystem.cxx + pandaLogger.cxx +) + +composite_sources(p3assimp P3ASSIMP_SOURCES) +add_library(p3assimp ${MODULE_TYPE} ${P3ASSIMP_HEADERS} ${P3ASSIMP_SOURCES}) +set_target_properties(p3assimp PROPERTIES DEFINE_SYMBOL BUILDING_ASSIMP) +target_link_libraries(p3assimp PRIVATE p3pandatoolbase) +target_link_libraries(p3assimp PUBLIC PKG::ASSIMP) + +if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$") + # Do not re-export symbols from these libraries. + target_link_options(p3assimp PRIVATE "LINKER:--exclude-libs,libassimp.a") + target_link_options(p3assimp PRIVATE "LINKER:--exclude-libs,libIrrXML.a") +endif() + +if(BUILD_SHARED_LIBS) + # We can't install this if we're doing a static build, because it depends on + # a static library that isn't installed. + install(TARGETS p3assimp EXPORT Assimp COMPONENT Assimp DESTINATION ${MODULE_DESTINATION}) +endif() diff --git a/pandatool/src/assimp/assimpLoader.I b/pandatool/src/assimp/assimpLoader.I new file mode 100644 index 00000000..5ce6c3bf --- /dev/null +++ b/pandatool/src/assimp/assimpLoader.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file assimpLoader.I + * @author rdb + * @date 2011-03-29 + */ diff --git a/pandatool/src/assimp/assimpLoader.cxx b/pandatool/src/assimp/assimpLoader.cxx new file mode 100644 index 00000000..80b94a7c --- /dev/null +++ b/pandatool/src/assimp/assimpLoader.cxx @@ -0,0 +1,1326 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file assimpLoader.cxx + * @author rdb + * @date 2011-03-29 + */ + +#include "assimpLoader.h" + +#include "geomNode.h" +#include "luse.h" +#include "geomVertexWriter.h" +#include "geomPoints.h" +#include "geomLines.h" +#include "geomTriangles.h" +#include "pnmFileTypeRegistry.h" +#include "pnmImage.h" +#include "alphaTestAttrib.h" +#include "materialAttrib.h" +#include "textureAttrib.h" +#include "cullFaceAttrib.h" +#include "transparencyAttrib.h" +#include "ambientLight.h" +#include "directionalLight.h" +#include "spotlight.h" +#include "pointLight.h" +#include "look_at.h" +#include "texturePool.h" +#include "character.h" +#include "animBundle.h" +#include "animBundleNode.h" +#include "animChannelMatrixXfmTable.h" +#include "pvector.h" +#include "cmath.h" +#include "deg_2_rad.h" +#include "string_utils.h" + +#include "pandaIOSystem.h" +#include "pandaLogger.h" + +#include + +#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR +#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0 +#endif + +#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR +#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0 +#endif + +#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR +#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0 +#endif + +#ifndef AI_MATKEY_GLTF_ALPHAMODE +#define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0 +#endif + +#ifndef AI_MATKEY_GLTF_ALPHACUTOFF +#define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0 +#endif + +// Older versions of Assimp used these glTF-specific keys instead. +#ifndef AI_MATKEY_BASE_COLOR +#define AI_MATKEY_BASE_COLOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR +#endif + +#ifndef AI_MATKEY_METALLIC_FACTOR +#define AI_MATKEY_METALLIC_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR +#endif + +#ifndef AI_MATKEY_ROUGHNESS_FACTOR +#define AI_MATKEY_ROUGHNESS_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR +#endif + +using std::ostringstream; +using std::stringstream; +using std::string; + +struct BoneWeight { + CPT(JointVertexTransform) joint_vertex_xform; + float weight; + + BoneWeight(CPT(JointVertexTransform) joint_vertex_xform, float weight) + : joint_vertex_xform(joint_vertex_xform), weight(weight) + {} +}; +typedef pvector BoneWeightList; + +/** + * + */ +AssimpLoader:: +AssimpLoader() : + _error (false), + _geoms (nullptr) { + + PandaLogger::set_default(); + _importer.SetIOHandler(new PandaIOSystem); +} + +/** + * + */ +AssimpLoader:: +~AssimpLoader() { + _importer.FreeScene(); +} + +/** + * Returns a space-separated list of extensions that Assimp can load, without + * the leading dots. + */ +void AssimpLoader:: +get_extensions(string &ext) const { + aiString aexts; + _importer.GetExtensionList(aexts); + + // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot + char *sub = strtok(aexts.data, ";"); + while (sub != nullptr) { + ext += sub + 2; + sub = strtok(nullptr, ";"); + + if (sub != nullptr) { + ext += ' '; + } + } +} + +/** + * Reads from the indicated file. + */ +bool AssimpLoader:: +read(const Filename &filename) { + _filename = filename; + + unsigned int flags = aiProcess_Triangulate | aiProcess_GenUVCoords; + + if (assimp_calc_tangent_space) { + flags |= aiProcess_CalcTangentSpace; + } + if (assimp_join_identical_vertices) { + flags |= aiProcess_JoinIdenticalVertices; + } + if (assimp_improve_cache_locality) { + flags |= aiProcess_ImproveCacheLocality; + } + if (assimp_remove_redundant_materials) { + flags |= aiProcess_RemoveRedundantMaterials; + } + if (assimp_fix_infacing_normals) { + flags |= aiProcess_FixInfacingNormals; + } + if (assimp_optimize_meshes) { + flags |= aiProcess_OptimizeMeshes; + } + if (assimp_optimize_graph) { + flags |= aiProcess_OptimizeGraph; + } + if (assimp_flip_winding_order) { + flags |= aiProcess_FlipWindingOrder; + } + if (assimp_gen_normals) { + if (assimp_smooth_normal_angle == 0.0) { + flags |= aiProcess_GenNormals; + } + else { + flags |= aiProcess_GenSmoothNormals; + _importer.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, + assimp_smooth_normal_angle); + } + } + + _scene = _importer.ReadFile(_filename.c_str(), flags); + if (_scene == nullptr) { + _error = true; + return false; + } + + _error = false; + return true; +} + +/** + * Converts scene graph structures into a Panda3D scene graph, with _root + * being the root node. + */ +void AssimpLoader:: +build_graph() { + nassertv(_scene != nullptr); // read() must be called first + nassertv(!_error); // and have succeeded + + // Protect the import process + MutexHolder holder(_lock); + + _root = new ModelRoot(_filename.get_basename()); + + // Import all of the embedded textures first. + _textures = new PT(Texture)[_scene->mNumTextures]; + for (size_t i = 0; i < _scene->mNumTextures; ++i) { + load_texture(i); + } + + // Then the materials. + _mat_states = new CPT(RenderState)[_scene->mNumMaterials]; + for (size_t i = 0; i < _scene->mNumMaterials; ++i) { + load_material(i); + } + + // And then the meshes. + _geoms = new Geoms[_scene->mNumMeshes]; + for (size_t i = 0; i < _scene->mNumMeshes; ++i) { + load_mesh(i); + } + + // And now the node structure. + if (_scene->mRootNode != nullptr) { + load_node(*_scene->mRootNode, _root); + } + + // And lastly, the lights. + for (size_t i = 0; i < _scene->mNumLights; ++i) { + load_light(*_scene->mLights[i]); + } + + delete[] _textures; + delete[] _mat_states; + delete[] _geoms; +} + +/** + * Finds a node by name. + */ +const aiNode *AssimpLoader:: +find_node(const aiNode &root, const aiString &name) { + const aiNode *node; + + if (root.mName == name) { + return &root; + } else { + for (size_t i = 0; i < root.mNumChildren; ++i) { + node = find_node(*root.mChildren[i], name); + if (node) { + return node; + } + } + } + + return nullptr; +} + +/** + * Converts an aiTexture into a Texture. + */ +void AssimpLoader:: +load_texture(size_t index) { + const aiTexture &tex = *_scene->mTextures[index]; + + PT(Texture) ptex = new Texture; + + if (tex.mHeight == 0) { + // Compressed texture. + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Reading embedded compressed texture with format " + << tex.achFormatHint << " and size " << tex.mWidth << "\n"; + } + stringstream str; + str.write((char*) tex.pcData, tex.mWidth); + + if (strncmp(tex.achFormatHint, "dds", 3) == 0) { + ptex->read_dds(str); + + } else { + const PNMFileTypeRegistry *reg = PNMFileTypeRegistry::get_global_ptr(); + PNMFileType *ftype; + PNMImage img; + + // Work around a bug in Assimp, it sometimes writes jp instead of jpg + if (strncmp(tex.achFormatHint, "jp\0", 3) == 0) { + ftype = reg->get_type_from_extension("jpg"); + } else { + ftype = reg->get_type_from_extension(tex.achFormatHint); + } + + if (img.read(str, "", ftype)) { + ptex->load(img); + } else { + ptex = nullptr; + } + } + } else { + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Reading embedded raw texture with size " + << tex.mWidth << "x" << tex.mHeight << "\n"; + } + + ptex->setup_2d_texture(tex.mWidth, tex.mHeight, Texture::T_unsigned_byte, Texture::F_rgba); + PTA_uchar data = ptex->modify_ram_image(); + + size_t p = 0; + for (size_t i = 0; i < tex.mWidth * tex.mHeight; ++i) { + const aiTexel &texel = tex.pcData[i]; + data[p++] = texel.b; + data[p++] = texel.g; + data[p++] = texel.r; + data[p++] = texel.a; + } + } + + // ostringstream path; path << "tmp" << index << ".png"; + // ptex->write(path.str()); + + _textures[index] = ptex; + +} + +/** + * Converts an aiMaterial into a RenderState. + */ +void AssimpLoader:: +load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, + TextureStage::Mode mode, CPT(TextureAttrib) &tattr, + CPT(TexMatrixAttrib) &tmattr) { + aiString path; + aiTextureMapping mapping; + unsigned int uvindex; + float blend; + aiTextureOp op; + aiTextureMapMode mapmode[3]; + + for (size_t i = 0; i < mat.GetTextureCount(ttype); ++i) { + mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, mapmode); + + if (AI_SUCCESS != mat.Get(AI_MATKEY_UVWSRC(ttype, i), uvindex)) { + // If there's no texture coordinate set for this texture, assume that + // it's the same as the index on the stack. TODO: if there's only one + // set on the mesh, force everything to use just the first stage. + uvindex = i; + } + + if (ttype == aiTextureType_DIFFUSE && i == 1) { + // The glTF 2 importer duplicates this slot in older versions of Assimp. + // Since glTF doesn't support multiple diffuse textures anyway, we check + // for this old glTF-specific key, and if present, ignore this texture. + aiColor4D col; + if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, col)) { + return; + } + } + + std::string uvindex_str = format_string(uvindex); + PT(TextureStage) stage = new TextureStage(uvindex_str); + stage->set_mode(mode); + if (uvindex > 0) { + stage->set_texcoord_name(InternalName::get_texcoord_name(uvindex_str)); + } + PT(Texture) ptex; + + // I'm not sure if this is the right way to handle it, as I couldn't find + // much information on embedded textures. + if (path.data[0] == '*') { + long num = strtol(path.data + 1, nullptr, 10); + ptex = _textures[num]; + + } else if (path.length > 0) { + Filename fn = Filename::from_os_specific(string(path.data, path.length)); + + // Try to find the file by moving up twice in the hierarchy. + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + Filename dir (_filename); + _filename.make_canonical(); + dir = _filename.get_dirname(); + + // Quake 3 BSP doesn't specify an extension for textures. + if (vfs->is_regular_file(Filename(dir, fn))) { + fn = Filename(dir, fn); + } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) { + fn = Filename(dir, fn + ".tga"); + } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) { + fn = Filename(dir, fn + ".jpg"); + } else { + dir = _filename.get_dirname(); + if (vfs->is_regular_file(Filename(dir, fn))) { + fn = Filename(dir, fn); + } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) { + fn = Filename(dir, fn + ".tga"); + } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) { + fn = Filename(dir, fn + ".jpg"); + } + } + + ptex = TexturePool::load_texture(fn); + } + + if (ptex != nullptr) { + // Apply the mapping modes. + switch (mapmode[0]) { + case aiTextureMapMode_Wrap: + ptex->set_wrap_u(SamplerState::WM_repeat); + break; + case aiTextureMapMode_Clamp: + ptex->set_wrap_u(SamplerState::WM_clamp); + break; + case aiTextureMapMode_Decal: + ptex->set_wrap_u(SamplerState::WM_border_color); + ptex->set_border_color(LColor(0, 0, 0, 0)); + break; + case aiTextureMapMode_Mirror: + ptex->set_wrap_u(SamplerState::WM_mirror); + break; + default: + break; + } + switch (mapmode[1]) { + case aiTextureMapMode_Wrap: + ptex->set_wrap_v(SamplerState::WM_repeat); + break; + case aiTextureMapMode_Clamp: + ptex->set_wrap_v(SamplerState::WM_clamp); + break; + case aiTextureMapMode_Decal: + ptex->set_wrap_v(SamplerState::WM_border_color); + ptex->set_border_color(LColor(0, 0, 0, 0)); + break; + case aiTextureMapMode_Mirror: + ptex->set_wrap_v(SamplerState::WM_mirror); + break; + default: + break; + } + switch (mapmode[2]) { + case aiTextureMapMode_Wrap: + ptex->set_wrap_w(SamplerState::WM_repeat); + break; + case aiTextureMapMode_Clamp: + ptex->set_wrap_w(SamplerState::WM_clamp); + break; + case aiTextureMapMode_Decal: + ptex->set_wrap_w(SamplerState::WM_border_color); + ptex->set_border_color(LColor(0, 0, 0, 0)); + break; + case aiTextureMapMode_Mirror: + ptex->set_wrap_w(SamplerState::WM_mirror); + break; + default: + break; + } + + tattr = DCAST(TextureAttrib, tattr->add_on_stage(stage, ptex)); + + // Is there a texture transform? + aiUVTransform transform; + if (AI_SUCCESS == mat.Get(AI_MATKEY_UVTRANSFORM(ttype, i), transform)) { + // Reconstruct the original origin from the glTF file. + PN_stdfloat rcos, rsin; + csincos(-transform.mRotation, &rsin, &rcos); + transform.mTranslation.x -= (0.5 * transform.mScaling.x) * (-rcos + rsin + 1); + transform.mTranslation.y -= ((0.5 * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y; + + LMatrix3 matrix = + LMatrix3::translate_mat(0, -1) * + LMatrix3::scale_mat(transform.mScaling.x, transform.mScaling.y) * + LMatrix3::rotate_mat(rad_2_deg(-transform.mRotation)) * + LMatrix3::translate_mat(transform.mTranslation.x, 1 + transform.mTranslation.y); + + CPT(TransformState) cstate = + TransformState::make_mat3(matrix); + + CPT(RenderAttrib) new_attr = (tmattr == nullptr) + ? TexMatrixAttrib::make(stage, std::move(cstate)) + : tmattr->add_stage(stage, std::move(cstate)); + tmattr = DCAST(TexMatrixAttrib, std::move(new_attr)); + } + } + } +} + +/** + * Converts an aiMaterial into a RenderState. + */ +void AssimpLoader:: +load_material(size_t index) { + const aiMaterial &mat = *_scene->mMaterials[index]; + + CPT(RenderState) state = RenderState::make_empty(); + + aiColor4D col; + bool have; + int ival; + PN_stdfloat fval; + + // XXX a lot of this is untested. + + // First do the material attribute. + PT(Material) pmat = new Material; + have = false; + if (AI_SUCCESS == mat.Get(AI_MATKEY_BASE_COLOR, col)) { + pmat->set_base_color(LColor(col.r, col.g, col.b, col.a)); + have = true; + } + else if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) { + pmat->set_diffuse(LColor(col.r, col.g, col.b, 1)); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_SPECULAR, col)) { + if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) { + pmat->set_specular(LColor(col.r * fval, col.g * fval, col.b * fval, 1)); + } else { + pmat->set_specular(LColor(col.r, col.g, col.b, 1)); + } + have = true; + } + //else { + // if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) { + // pmat->set_specular(LColor(fval, fval, fval, 1)); + // } else { + // pmat->set_specular(LColor(1, 1, 1, 1)); + // } + //} + if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_AMBIENT, col)) { + pmat->set_specular(LColor(col.r, col.g, col.b, 1)); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_EMISSIVE, col)) { + pmat->set_emission(LColor(col.r, col.g, col.b, 1)); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_TRANSPARENT, col)) { + // FIXME: ??? + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS, fval)) { + pmat->set_shininess(fval); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_METALLIC_FACTOR, fval)) { + pmat->set_metallic(fval); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, fval)) { + pmat->set_roughness(fval); + have = true; + } + if (AI_SUCCESS == mat.Get(AI_MATKEY_REFRACTI, fval)) { + pmat->set_refractive_index(fval); + have = true; + } + else if (pmat->has_metallic()) { + // Default refractive index to 1.5 for PBR models + pmat->set_refractive_index(1.5); + } + if (have) { + state = state->add_attrib(MaterialAttrib::make(pmat)); + } + + // Wireframe. + if (AI_SUCCESS == mat.Get(AI_MATKEY_ENABLE_WIREFRAME, ival)) { + if (ival) { + state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_wireframe)); + } else { + state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_filled)); + } + } + + // Backface culling. Not sure if this is also supposed to set the twoside + // flag in the material, I'm guessing not. + if (AI_SUCCESS == mat.Get(AI_MATKEY_TWOSIDED, ival)) { + if (ival) { + state = state->add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none)); + } else { + state = state->add_attrib(CullFaceAttrib::make_default()); + } + } + + // Alpha mode. + aiString alpha_mode; + if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alpha_mode)) { + if (strcmp(alpha_mode.C_Str(), "MASK") == 0) { + PN_stdfloat cutoff = 0.5; + mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, cutoff); + state = state->add_attrib(AlphaTestAttrib::make(AlphaTestAttrib::M_greater_equal, cutoff)); + } + else if (strcmp(alpha_mode.C_Str(), "BLEND") == 0) { + state = state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha)); + } + } + + // And let's not forget the textures! + CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make()); + CPT(TexMatrixAttrib) tmattr; + load_texture_stage(mat, aiTextureType_DIFFUSE, TextureStage::M_modulate, tattr, tmattr); + + // Check for an ORM map, from the glTF/OBJ importer. glTF also puts it in the + // LIGHTMAP slot, despite only having the lightmap in the red channel, so we + // have to ignore it. + if (mat.GetTextureCount(aiTextureType_UNKNOWN) > 0) { + load_texture_stage(mat, aiTextureType_UNKNOWN, TextureStage::M_selector, tattr, tmattr); + } else { + load_texture_stage(mat, aiTextureType_LIGHTMAP, TextureStage::M_modulate, tattr, tmattr); + } + + load_texture_stage(mat, aiTextureType_NORMALS, TextureStage::M_normal, tattr, tmattr); + load_texture_stage(mat, aiTextureType_EMISSIVE, TextureStage::M_emission, tattr, tmattr); + load_texture_stage(mat, aiTextureType_HEIGHT, TextureStage::M_height, tattr, tmattr); + if (tattr->get_num_on_stages() > 0) { + state = state->add_attrib(tattr); + } + if (tmattr != nullptr) { + state = state->add_attrib(tmattr); + } + + _mat_states[index] = std::move(state); +} + +/** + * Creates a CharacterJoint from an aiNode + */ +void AssimpLoader:: +create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node) { + const aiMatrix4x4 &t = node.mTransformation; + LMatrix4 mat(t.a1, t.b1, t.c1, t.d1, + t.a2, t.b2, t.c2, t.d2, + t.a3, t.b3, t.c3, t.d3, + t.a4, t.b4, t.c4, t.d4); + PT(CharacterJoint) joint = new CharacterJoint(character, bundle, parent, node.mName.C_Str(), mat); + + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Creating joint for: " << node.mName.C_Str() << "\n"; + } + + for (size_t i = 0; i < node.mNumChildren; ++i) { + if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) { + create_joint(character, bundle, joint, *node.mChildren[i]); + } + } +} + +/** + * Creates a AnimChannelMatrixXfmTable from an aiNodeAnim + */ +void AssimpLoader:: +create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node) { + PT(AnimChannelMatrixXfmTable) group = new AnimChannelMatrixXfmTable(parent, node.mName.C_Str()); + + // See if there is a channel for this node + aiNodeAnim *node_anim = nullptr; + for (size_t i = 0; i < anim.mNumChannels; ++i) { + if (anim.mChannels[i]->mNodeName == node.mName) { + node_anim = anim.mChannels[i]; + } + } + + if (node_anim) { + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Found channel for node: " << node.mName.C_Str() << "\n"; + } + // assimp_cat.debug() << "Num Position Keys " << + // node_anim->mNumPositionKeys << "\n"; assimp_cat.debug() << "Num + // Rotation Keys " << node_anim->mNumRotationKeys << "\n"; + // assimp_cat.debug() << "Num Scaling Keys " << node_anim->mNumScalingKeys + // << "\n"; + + // Convert positions + PTA_stdfloat tablex = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys); + PTA_stdfloat tabley = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys); + PTA_stdfloat tablez = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys); + for (size_t i = 0; i < node_anim->mNumPositionKeys; ++i) { + tablex[i] = node_anim->mPositionKeys[i].mValue.x; + tabley[i] = node_anim->mPositionKeys[i].mValue.y; + tablez[i] = node_anim->mPositionKeys[i].mValue.z; + } + group->set_table('x', tablex); + group->set_table('y', tabley); + group->set_table('z', tablez); + + // Convert rotations + PTA_stdfloat tableh = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys); + PTA_stdfloat tablep = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys); + PTA_stdfloat tabler = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys); + for (size_t i = 0; i < node_anim->mNumRotationKeys; ++i) { + aiQuaternion ai_quat = node_anim->mRotationKeys[i].mValue; + LVecBase3 hpr = LQuaternion(ai_quat.w, ai_quat.x, ai_quat.y, ai_quat.z).get_hpr(); + tableh[i] = hpr.get_x(); + tablep[i] = hpr.get_y(); + tabler[i] = hpr.get_z(); + } + group->set_table('h', tableh); + group->set_table('p', tablep); + group->set_table('r', tabler); + + // Convert scales + PTA_stdfloat tablei = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys); + PTA_stdfloat tablej = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys); + PTA_stdfloat tablek = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys); + for (size_t i = 0; i < node_anim->mNumScalingKeys; ++i) { + tablei[i] = node_anim->mScalingKeys[i].mValue.x; + tablej[i] = node_anim->mScalingKeys[i].mValue.y; + tablek[i] = node_anim->mScalingKeys[i].mValue.z; + } + group->set_table('i', tablei); + group->set_table('j', tablej); + group->set_table('k', tablek); + } + else if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "No channel found for node: " << node.mName.C_Str() << "\n"; + } + + + for (size_t i = 0; i < node.mNumChildren; ++i) { + if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) { + create_anim_channel(anim, bundle, group, *node.mChildren[i]); + } + } +} + +/** + * Converts an aiMesh into a Geom. + */ +void AssimpLoader:: +load_mesh(size_t index) { + const aiMesh &mesh = *_scene->mMeshes[index]; + + // Check if we need to make a Character + PT(Character) character = nullptr; + if (mesh.HasBones()) { + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Creating character for mesh '" << mesh.mName.C_Str() << "' with " + << mesh.mNumBones << " bones\n"; + } + + // Find and add all bone nodes to the bone map + for (size_t i = 0; i < mesh.mNumBones; ++i) { + const aiBone &bone = *mesh.mBones[i]; + const aiNode *node = find_node(*_scene->mRootNode, bone.mName); + _bonemap[bone.mName.C_Str()] = node; + } + + // Now create a character from the bones + character = new Character(mesh.mName.C_Str()); + PT(CharacterJointBundle) bundle = character->get_bundle(0); + PT(PartGroup) skeleton = new PartGroup(bundle, ""); + + for (size_t i = 0; i < mesh.mNumBones; ++i) { + const aiBone &bone = *mesh.mBones[i]; + + // Find the root bone node + const aiNode *root = _bonemap[bone.mName.C_Str()]; + while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) { + root = root->mParent; + } + + // Don't process this root if we already have a joint for it + if (character->find_joint(root->mName.C_Str())) { + continue; + } + + create_joint(character, bundle, skeleton, *root); + } + } + + // Create transform blend table + PT(TransformBlendTable) tbtable = new TransformBlendTable; + pvector bone_weights(mesh.mNumVertices); + if (character) { + for (size_t i = 0; i < mesh.mNumBones; ++i) { + const aiBone &bone = *mesh.mBones[i]; + CharacterJoint *joint = character->find_joint(bone.mName.C_Str()); + if (joint == nullptr) { + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Could not find joint for bone: " << bone.mName.C_Str() << "\n"; + } + continue; + } + + CPT(JointVertexTransform) jvt = new JointVertexTransform(joint); + + for (size_t j = 0; j < bone.mNumWeights; ++j) { + const aiVertexWeight &weight = bone.mWeights[j]; + + bone_weights[weight.mVertexId].push_back(BoneWeight(jvt, weight.mWeight)); + } + } + } + + // Create the vertex format. + PT(GeomVertexArrayFormat) aformat = new GeomVertexArrayFormat; + aformat->add_column(InternalName::get_vertex(), 3, Geom::NT_stdfloat, Geom::C_point); + if (mesh.HasNormals()) { + aformat->add_column(InternalName::get_normal(), 3, Geom::NT_stdfloat, Geom::C_normal); + } + if (mesh.HasVertexColors(0)) { + aformat->add_column(InternalName::get_color(), 4, Geom::NT_stdfloat, Geom::C_color); + } + unsigned int num_uvs = mesh.GetNumUVChannels(); + if (num_uvs > 0) { + // UV sets are named texcoord, texcoord.1, texcoord.2... + aformat->add_column(InternalName::get_texcoord(), 3, Geom::NT_stdfloat, Geom::C_texcoord); + for (unsigned int u = 1; u < num_uvs; ++u) { + ostringstream out; + out << u; + aformat->add_column(InternalName::get_texcoord_name(out.str()), 3, Geom::NT_stdfloat, Geom::C_texcoord); + } + } + if (mesh.HasTangentsAndBitangents()) { + aformat->add_column(InternalName::get_tangent(), 3, Geom::NT_stdfloat, Geom::C_vector); + aformat->add_column(InternalName::get_binormal(), 3, Geom::NT_stdfloat, Geom::C_vector); + } + + PT(GeomVertexArrayFormat) tb_aformat = new GeomVertexArrayFormat; + tb_aformat->add_column(InternalName::make("transform_blend"), 1, Geom::NT_uint16, Geom::C_index); + + // Check to see if we need to convert any animations + for (size_t i = 0; i < _scene->mNumAnimations; ++i) { + aiAnimation &ai_anim = *_scene->mAnimations[i]; + bool convert_anim = false; + + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Checking to see if anim (" << ai_anim.mName.C_Str() + << ") matches character (" << mesh.mName.C_Str() << ")\n"; + } + for (size_t j = 0; j < ai_anim.mNumChannels; ++j) { + if (assimp_cat.is_spam()) { + assimp_cat.spam() + << "Searching for " << ai_anim.mChannels[j]->mNodeName.C_Str() + << " in bone map" << "\n"; + } + if (_bonemap.find(ai_anim.mChannels[j]->mNodeName.C_Str()) != _bonemap.end()) { + convert_anim = true; + break; + } + } + + if (convert_anim) { + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Found animation (" << ai_anim.mName.C_Str() + << ") for character (" << mesh.mName.C_Str() << ")\n"; + } + + // Now create the animation + unsigned int frames = 0; + for (size_t j = 0; j < ai_anim.mNumChannels; ++j) { + if (ai_anim.mChannels[j]->mNumPositionKeys > frames) { + frames = ai_anim.mChannels[j]->mNumPositionKeys; + } + if (ai_anim.mChannels[j]->mNumRotationKeys > frames) { + frames = ai_anim.mChannels[j]->mNumRotationKeys; + } + if (ai_anim.mChannels[j]->mNumScalingKeys > frames) { + frames = ai_anim.mChannels[j]->mNumScalingKeys; + } + } + PN_stdfloat fps = frames / (ai_anim.mTicksPerSecond * ai_anim.mDuration); + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "FPS " << fps << "\n"; + assimp_cat.debug() + << "Frames " << frames << "\n"; + } + + PT(AnimBundle) bundle = new AnimBundle(mesh.mName.C_Str(), fps, frames); + PT(AnimGroup) skeleton = new AnimGroup(bundle, ""); + + for (size_t i = 0; i < mesh.mNumBones; ++i) { + const aiBone &bone = *mesh.mBones[i]; + + // Find the root bone node + const aiNode *root = _bonemap[bone.mName.C_Str()]; + while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) { + root = root->mParent; + } + + // Only convert root nodes + if (root->mName == bone.mName) { + create_anim_channel(ai_anim, bundle, skeleton, *root); + + // Attach the animation to the character node + PT(AnimBundleNode) bundle_node = new AnimBundleNode(bone.mName.C_Str(), bundle); + character->add_child(bundle_node); + } + } + } + } + + // TODO: if there is only one UV set, hackily iterate over the texture + // stages and clear the texcoord name things + + PT(GeomVertexFormat) format = new GeomVertexFormat; + format->add_array(aformat); + if (character) { + format->add_array(tb_aformat); + + GeomVertexAnimationSpec aspec; + aspec.set_panda(); + format->set_animation(aspec); + } + + // Create the GeomVertexData. + string name (mesh.mName.data, mesh.mName.length); + PT(GeomVertexData) vdata = new GeomVertexData(name, GeomVertexFormat::register_format(format), Geom::UH_static); + if (character) { + vdata->set_transform_blend_table(tbtable); + } + vdata->unclean_set_num_rows(mesh.mNumVertices); + + // Read out the vertices. + GeomVertexWriter vertex (vdata, InternalName::get_vertex()); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &vec = mesh.mVertices[i]; + vertex.set_data3(vec.x, vec.y, vec.z); + } + + // Now the normals, if any. + if (mesh.HasNormals()) { + GeomVertexWriter normal (vdata, InternalName::get_normal()); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &vec = mesh.mNormals[i]; + normal.set_data3(vec.x, vec.y, vec.z); + } + } + + // Vertex colors, if any. We only import the first set. + if (mesh.HasVertexColors(0)) { + GeomVertexWriter color (vdata, InternalName::get_color()); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiColor4D &col = mesh.mColors[0][i]; + color.set_data4(col.r, col.g, col.b, col.a); + } + } + + // Now the texture coordinates. + if (num_uvs > 0) { + // UV sets are named texcoord, texcoord.1, texcoord.2... + GeomVertexWriter texcoord0 (vdata, InternalName::get_texcoord()); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &vec = mesh.mTextureCoords[0][i]; + texcoord0.set_data3(vec.x, vec.y, vec.z); + } + for (unsigned int u = 1; u < num_uvs; ++u) { + ostringstream out; + out << u; + GeomVertexWriter texcoord (vdata, InternalName::get_texcoord_name(out.str())); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &vec = mesh.mTextureCoords[u][i]; + texcoord.set_data3(vec.x, vec.y, vec.z); + } + } + } + + // Now the tangents and bitangents, if any. + if (mesh.HasTangentsAndBitangents()) { + GeomVertexWriter tangent (vdata, InternalName::get_tangent()); + GeomVertexWriter binormal (vdata, InternalName::get_binormal()); + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &tvec = mesh.mTangents[i]; + const aiVector3D &bvec = mesh.mBitangents[i]; + tangent.set_data3(tvec.x, tvec.y, tvec.z); + binormal.set_data3(bvec.x, bvec.y, bvec.z); + } + } + + // Now the transform blend table + if (character) { + GeomVertexWriter transform_blend (vdata, InternalName::get_transform_blend()); + + for (size_t i = 0; i < mesh.mNumVertices; ++i) { + TransformBlend tblend; + + for (size_t j = 0; j < bone_weights[i].size(); ++j) { + tblend.add_transform(bone_weights[i][j].joint_vertex_xform, bone_weights[i][j].weight); + } + transform_blend.set_data1i(tbtable->add_blend(tblend)); + } + + tbtable->set_rows(SparseArray::lower_on(vdata->get_num_rows())); + } + + // Now read out the primitives. Keep in mind that we called ReadFile with + // the aiProcess_Triangulate flag earlier, so we don't have to worry about + // polygons. + PT(GeomPoints) points = new GeomPoints(Geom::UH_static); + PT(GeomLines) lines = new GeomLines(Geom::UH_static); + PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static); + + // Now add the vertex indices. + for (size_t i = 0; i < mesh.mNumFaces; ++i) { + const aiFace &face = mesh.mFaces[i]; + + if (face.mNumIndices == 0) { + // It happens, strangely enough. + continue; + } else if (face.mNumIndices == 1) { + points->add_vertex(face.mIndices[0]); + points->close_primitive(); + } else if (face.mNumIndices == 2) { + lines->add_vertices(face.mIndices[0], face.mIndices[1]); + lines->close_primitive(); + } else if (face.mNumIndices == 3) { + triangles->add_vertices(face.mIndices[0], face.mIndices[1], face.mIndices[2]); + triangles->close_primitive(); + } else { + nassertd(false) continue; + } + } + + // Create a geom and add the primitives to it. + Geoms &geoms = _geoms[index]; + geoms._mat_index = mesh.mMaterialIndex; + + if (points->get_num_primitives() > 0) { + geoms._points = new Geom(vdata); + geoms._points->add_primitive(points); + } + if (lines->get_num_primitives() > 0) { + geoms._lines = new Geom(vdata); + geoms._lines->add_primitive(lines); + } + if (triangles->get_num_primitives() > 0) { + geoms._triangles = new Geom(vdata); + geoms._triangles->add_primitive(triangles); + } + + if (character) { + geoms._character = character; + + PT(GeomNode) gnode = new GeomNode(""); + if (geoms._points != nullptr) { + gnode->add_geom(geoms._points); + } + if (geoms._lines != nullptr) { + gnode->add_geom(geoms._lines); + } + if (geoms._triangles != nullptr) { + gnode->add_geom(geoms._triangles); + } + gnode->set_state(_mat_states[mesh.mMaterialIndex]); + character->add_child(gnode); + } +} + +/** + * Converts an aiNode into a PandaNode. Returns true if the node had anything + * of interest under it, false otherwise. + */ +bool AssimpLoader:: +load_node(const aiNode &node, PandaNode *parent, bool under_joint) { + PT(PandaNode) pnode; + string name (node.mName.data, node.mName.length); + + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Converting node '" << name << "' with " << node.mNumMeshes + << " meshes and " << node.mNumChildren << " children\n"; + } + + if (!under_joint) { + under_joint = (_bonemap.find(node.mName.C_Str()) != _bonemap.end()); + } + + bool prune = false; + + if (node.mNumMeshes == 0) { + if (parent == _root && assimp_collapse_dummy_root_node && !under_joint && + (name.empty() || name[0] == '$' || name == "RootNode" || name == "ROOT" || name == "Root" || (name.size() > 2 && name[0] == '<' && name[name.size() - 1] == '>') || name == _root->get_name())) { + // Collapse root node. + pnode = _root; + } else { + pnode = new PandaNode(name); + + // Possibly prune this if this is a joint or under a joint. + prune = under_joint; + } + } + else if (node.mNumMeshes == 1) { + size_t meshIndex = node.mMeshes[0]; + const Geoms &geoms = _geoms[meshIndex]; + + if (geoms._character != nullptr) { + pnode = new PandaNode(name); + pnode->add_child(geoms._character); + } + else { + PT(GeomNode) gnode = new GeomNode(name); + const RenderState *state = _mat_states[geoms._mat_index]; + if (geoms._points != nullptr) { + gnode->add_geom(geoms._points); + } + if (geoms._lines != nullptr) { + gnode->add_geom(geoms._lines); + } + if (geoms._triangles != nullptr) { + gnode->add_geom(geoms._triangles); + } + if (state != nullptr) { + // Only set the state on the GeomNode if there are no child nodes. + if (node.mNumChildren == 0) { + gnode->set_state(state); + } else { + for (int i = 0; i < gnode->get_num_geoms(); ++i) { + gnode->set_geom_state(i, state); + } + } + } + pnode = gnode; + } + } + else { + // Do we have regular meshes or just animated meshes? + bool character_only = true; + + // First add all the regular meshes. + for (size_t i = 0; i < node.mNumMeshes; ++i) { + size_t meshIndex = node.mMeshes[i]; + + if (_geoms[meshIndex]._character == nullptr) { + character_only = false; + break; + } + } + + PT(GeomNode) gnode; + if (character_only) { + pnode = new PandaNode(name); + } else { + gnode = new GeomNode(name); + pnode = gnode; + } + + for (size_t i = 0; i < node.mNumMeshes; ++i) { + size_t meshIndex = node.mMeshes[i]; + const Geoms &geoms = _geoms[meshIndex]; + + if (geoms._character != nullptr) { + // An animated mesh, which already is converted as Character with an + // attached GeomNode. + pnode->add_child(geoms._character); + } + else { + // A non-animated mesh. + const RenderState *state = _mat_states[geoms._mat_index]; + if (geoms._points != nullptr) { + gnode->add_geom(geoms._points, state); + } + if (geoms._lines != nullptr) { + gnode->add_geom(geoms._lines, state); + } + if (geoms._triangles != nullptr) { + gnode->add_geom(geoms._triangles, state); + } + } + } + } + + if (parent != pnode) { + parent->add_child(pnode); + } + + if (node.mMetaData != nullptr) { + for (unsigned i = 0; i < node.mMetaData->mNumProperties; ++i) { + const aiMetadataEntry &entry = node.mMetaData->mValues[i]; + std::string value; + switch (entry.mType) { + //case AI_BOOL: + // value = (*static_cast(entry.mData)) ? "1" : ""; + // break; + case AI_INT32: + value = format_string(*static_cast(entry.mData)); + break; + case AI_UINT64: + value = format_string(*static_cast(entry.mData)); + break; + case AI_FLOAT: + value = format_string(*static_cast(entry.mData)); + break; + case AI_DOUBLE: + value = format_string(*static_cast(entry.mData)); + break; + case AI_AISTRING: + { + const aiString *str = static_cast(entry.mData); + value = std::string(str->data, str->length); + } + break; + default: + continue; + } + const aiString &key = node.mMetaData->mKeys[i]; + pnode->set_tag(std::string(key.data, key.length), std::move(value)); + } + } + + // Load in the transformation matrix. + const aiMatrix4x4 &t = node.mTransformation; + if (!t.IsIdentity()) { + LMatrix4 mat(t.a1, t.b1, t.c1, t.d1, + t.a2, t.b2, t.c2, t.d2, + t.a3, t.b3, t.c3, t.d3, + t.a4, t.b4, t.c4, t.d4); + pnode->set_transform(TransformState::make_mat(mat)); + } + + for (size_t i = 0; i < node.mNumChildren; ++i) { + if (load_node(*node.mChildren[i], pnode, under_joint)) { + prune = false; + } + } + + if (prune) { + // This is an empty node in a hierarchy of joints, prune it. + parent->remove_child(pnode); + if (assimp_cat.is_debug()) { + assimp_cat.debug() + << "Pruning node '" << name << "'\n"; + } + return false; + } else { + return true; + } +} + +/** + * Converts an aiLight into a LightNode. + */ +void AssimpLoader:: +load_light(const aiLight &light) { + string name (light.mName.data, light.mName.length); + if (assimp_cat.is_debug()) { + assimp_cat.debug() << "Found light '" << name << "'\n"; + } + + aiColor3D col; + aiVector3D vec; + + switch (light.mType) { + case aiLightSource_DIRECTIONAL: { + PT(DirectionalLight) dlight = new DirectionalLight(name); + _root->add_child(dlight); + + col = light.mColorDiffuse; + dlight->set_color(LColor(col.r, col.g, col.b, 1)); + + col = light.mColorSpecular; + dlight->set_specular_color(LColor(col.r, col.g, col.b, 1)); + + vec = light.mPosition; + dlight->set_point(LPoint3(vec.x, vec.y, vec.z)); + + vec = light.mDirection; + dlight->set_direction(LVector3(vec.x, vec.y, vec.z)); + break; } + + case aiLightSource_POINT: { + PT(PointLight) plight = new PointLight(name); + _root->add_child(plight); + + col = light.mColorDiffuse; + plight->set_color(LColor(col.r, col.g, col.b, 1)); + + col = light.mColorSpecular; + plight->set_specular_color(LColor(col.r, col.g, col.b, 1)); + + vec = light.mPosition; + plight->set_point(LPoint3(vec.x, vec.y, vec.z)); + + plight->set_attenuation(LVecBase3(light.mAttenuationConstant, + light.mAttenuationLinear, + light.mAttenuationQuadratic)); + break; } + + case aiLightSource_SPOT: { + PT(Spotlight) plight = new Spotlight(name); + _root->add_child(plight); + + col = light.mColorDiffuse; + plight->set_color(LColor(col.r, col.g, col.b, 1)); + + col = light.mColorSpecular; + plight->set_specular_color(LColor(col.r, col.g, col.b, 1)); + + plight->set_attenuation(LVecBase3(light.mAttenuationConstant, + light.mAttenuationLinear, + light.mAttenuationQuadratic)); + + plight->get_lens()->set_fov(light.mAngleOuterCone); + // TODO: translate mAngleInnerCone to an exponent, somehow + + // This *should* be about right. + vec = light.mDirection; + LPoint3 pos (light.mPosition.x, light.mPosition.y, light.mPosition.z); + LQuaternion quat; + ::look_at(quat, LPoint3(vec.x, vec.y, vec.z), LVector3::up()); + plight->set_transform(TransformState::make_pos_quat(pos, quat)); + break; } + + case aiLightSource_AMBIENT: + // This is handled below. + break; + + default: + assimp_cat.warning() << "Light '" << name << "' has an unknown type!\n"; + return; + } + + // If there's an ambient color, add it as ambient light. + col = light.mColorAmbient; + LVecBase4 ambient (col.r, col.g, col.b, 0); + if (ambient != LVecBase4::zero()) { + PT(AmbientLight) alight = new AmbientLight(name); + alight->set_color(ambient); + _root->add_child(alight); + } +} diff --git a/pandatool/src/assimp/assimpLoader.h b/pandatool/src/assimp/assimpLoader.h new file mode 100644 index 00000000..e323cb1e --- /dev/null +++ b/pandatool/src/assimp/assimpLoader.h @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file assimpLoader.h + * @author rdb + * @date 2011-03-29 + */ + +#ifndef ASSIMPLOADER_H +#define ASSIMPLOADER_H + +#include "config_assimp.h" +#include "filename.h" +#include "modelRoot.h" +#include "texture.h" +#include "textureStage.h" +#include "pmap.h" + +#include +#include + +class Character; +class CharacterJointBundle; +class PartGroup; +class AnimBundle; +class AnimGroup; + +struct char_cmp { + bool operator () (const char *a, const char *b) const { + return strcmp(a,b) < 0; + } +}; +typedef pmap BoneMap; + +/** + * Class that interfaces with Assimp and builds Panda nodes to represent the + * Assimp structures. The loader should be reusable. + */ +class AssimpLoader : public TypedReferenceCount { +public: + AssimpLoader(); + virtual ~AssimpLoader(); + + void get_extensions(std::string &ext) const; + + bool read(const Filename &filename); + void build_graph(); + +public: + bool _error; + PT(ModelRoot) _root; + Filename _filename; + Mutex _lock; + +private: + Assimp::Importer _importer; + const aiScene *_scene; + + struct Geoms { + PT(Geom) _points; + PT(Geom) _lines; + PT(Geom) _triangles; + PT(Character) _character; + unsigned int _mat_index = 0; + }; + + // These arrays are temporarily used during the build_graph run. + PT(Texture) *_textures; + CPT(RenderState) *_mat_states; + Geoms *_geoms; + BoneMap _bonemap; + + const aiNode *find_node(const aiNode &root, const aiString &name); + + void load_texture(size_t index); + void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, + TextureStage::Mode mode, CPT(TextureAttrib) &tattr, + CPT(TexMatrixAttrib) &tmattr); + void load_material(size_t index); + void create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node); + void create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node); + void load_mesh(size_t index); + bool load_node(const aiNode &node, PandaNode *parent, bool under_joint = false); + void load_light(const aiLight &light); +}; + +#include "assimpLoader.I" + +#endif diff --git a/pandatool/src/assimp/config_assimp.cxx b/pandatool/src/assimp/config_assimp.cxx new file mode 100644 index 00000000..42446e2b --- /dev/null +++ b/pandatool/src/assimp/config_assimp.cxx @@ -0,0 +1,114 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_assimp.cxx + * @author rdb + * @date 2011-03-29 + */ + +#include "config_assimp.h" + +#include "loaderFileTypeAssimp.h" + +#include "dconfig.h" +#include "loaderFileTypeRegistry.h" + +ConfigureDef(config_assimp); +NotifyCategoryDef(assimp, ""); + +ConfigureFn(config_assimp) { + init_libassimp(); +} + +ConfigVariableBool assimp_calc_tangent_space +("assimp-calc-tangent-space", false, + PRC_DESC("Calculates tangents and binormals for meshes imported via Assimp.")); + +ConfigVariableBool assimp_join_identical_vertices +("assimp-join-identical-vertices", true, + PRC_DESC("Merges duplicate vertices. Set this to false if you want each " + "vertex to only be in use on one triangle.")); + +ConfigVariableBool assimp_improve_cache_locality +("assimp-improve-cache-locality", true, + PRC_DESC("Improves rendering performance of the loaded meshes by reordering " + "triangles for better vertex cache locality. Set this to false if " + "you need geometry to be loaded in the exact order that it was " + "specified in the file, or to improve load performance.")); + +ConfigVariableBool assimp_remove_redundant_materials +("assimp-remove-redundant-materials", true, + PRC_DESC("Removes redundant/unreferenced materials from assets.")); + +ConfigVariableBool assimp_fix_infacing_normals +("assimp-fix-infacing-normals", false, + PRC_DESC("Determines which normal vectors are facing inward and inverts them " + "so that they are facing outward.")); + +ConfigVariableBool assimp_optimize_meshes +("assimp-optimize-meshes", true, + PRC_DESC("Reduces the number of draw calls by unifying geometry with the same " + "materials. Especially effective in conjunction with " + "assimp-optimize-graph and assimp-remove-redundant-materials.")); + +ConfigVariableBool assimp_optimize_graph +("assimp-optimize-graph", false, + PRC_DESC("Optimizes the scene geometry by flattening the scene hierarchy. " + "This is very efficient (combined with assimp-optimize-meshes), but " + "it may result the hierarchy to become lost, so it is disabled by " + "default.")); + +ConfigVariableBool assimp_flip_winding_order +("assimp-flip-winding-order", false, + PRC_DESC("Set this true to flip the winding order of all models loaded via " + "the Assimp loader. Note that you may need to clear the model-cache " + "after changing this.")); + +ConfigVariableBool assimp_gen_normals +("assimp-gen-normals", false, + PRC_DESC("Set this true to generate normals (if absent from file) on import. " + "See assimp-smooth-normal-angle for more information. " + "Note that you may need to clear the model-cache after " + "changing this.")); + +ConfigVariableDouble assimp_smooth_normal_angle +("assimp-smooth-normal-angle", 0.0, + PRC_DESC("Set this to anything other than 0.0 in degrees (so 180.0 is PI) to " + "specify the maximum angle that may be between two face normals at " + "the same vertex position that are smoothed together. Sometimes " + "referred to as 'crease angle'. Only has effect if " + "assimp-gen-normals is set to true and the file does not contain " + "normals. Note that you may need to clear the model-cache after " + "changing this.")); + +ConfigVariableBool assimp_collapse_dummy_root_node +("assimp-collapse-dummy-root-node", true, + PRC_DESC("If set to true, collapses the root node that Assimp creates, if it " + "appears to be a synthetic dummy root node and contains no meshes. " + "This variable is new as of Panda3D 1.10.13 and will become true by " + "default as of Panda3D 1.11.0.")); + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libassimp() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + LoaderFileTypeAssimp::init_type(); + + LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr(); + reg->register_type(new LoaderFileTypeAssimp); +} diff --git a/pandatool/src/assimp/config_assimp.h b/pandatool/src/assimp/config_assimp.h new file mode 100644 index 00000000..f999a600 --- /dev/null +++ b/pandatool/src/assimp/config_assimp.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_assimp.h + * @author rdb + * @date 2011-03-29 + */ + +#ifndef CONFIG_ASSIMP_H +#define CONFIG_ASSIMP_H + +#include "pandatoolbase.h" +#include "notifyCategoryProxy.h" +#include "configVariableBool.h" +#include "configVariableDouble.h" +#include "dconfig.h" + +ConfigureDecl(config_assimp, EXPCL_ASSIMP, EXPTP_ASSIMP); +NotifyCategoryDecl(assimp, EXPCL_ASSIMP, EXPTP_ASSIMP); + +extern ConfigVariableBool assimp_calc_tangent_space; +extern ConfigVariableBool assimp_join_identical_vertices; +extern ConfigVariableBool assimp_improve_cache_locality; +extern ConfigVariableBool assimp_remove_redundant_materials; +extern ConfigVariableBool assimp_fix_infacing_normals; +extern ConfigVariableBool assimp_optimize_meshes; +extern ConfigVariableBool assimp_optimize_graph; +extern ConfigVariableBool assimp_flip_winding_order; +extern ConfigVariableBool assimp_gen_normals; +extern ConfigVariableDouble assimp_smooth_normal_angle; +extern ConfigVariableBool assimp_collapse_dummy_root_node; + +extern EXPCL_ASSIMP void init_libassimp(); + +#endif diff --git a/pandatool/src/assimp/loaderFileTypeAssimp.cxx b/pandatool/src/assimp/loaderFileTypeAssimp.cxx new file mode 100644 index 00000000..bd3061f2 --- /dev/null +++ b/pandatool/src/assimp/loaderFileTypeAssimp.cxx @@ -0,0 +1,133 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file loaderFileTypeAssimp.cxx + * @author rdb + * @date 2011-03-29 + */ + +#include "loaderFileTypeAssimp.h" +#include "config_assimp.h" +#include "assimpLoader.h" + +#include + +using std::string; + +TypeHandle LoaderFileTypeAssimp::_type_handle; + +/** + * + */ +LoaderFileTypeAssimp:: +LoaderFileTypeAssimp() { +} + +/** + * + */ +LoaderFileTypeAssimp:: +~LoaderFileTypeAssimp() { +} + +/** + * + */ +string LoaderFileTypeAssimp:: +get_name() const { + return "Assimp Importer"; +} + +/** + * + */ +string LoaderFileTypeAssimp:: +get_extension() const { + return ""; +} + +/** + * Returns a space-separated list of extension, in addition to the one + * returned by get_extension(), that are recognized by this converter. + */ +string LoaderFileTypeAssimp:: +get_additional_extensions() const { + // This may be called at static init time, so ensure it is constructed now. + static ConfigVariableString assimp_disable_extensions + ("assimp-disable-extensions", "gltf glb", + PRC_DESC("A list of extensions (without preceding dot) that should not be " + "loaded via the Assimp loader, even if Assimp supports these " + "formats. It is useful to set this for eg. gltf and glb files " + "to prevent them from being accidentally loaded via the Assimp " + "plug-in instead of via a superior plug-in like panda3d-gltf.")); + + bool has_disabled_exts = !assimp_disable_extensions.empty(); + + aiString aexts; + aiGetExtensionList(&aexts); + + char *buffer = (char *)alloca(aexts.length + 2); + char *p = buffer; + + // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot + char *sub = strtok(aexts.data, ";"); + while (sub != nullptr) { + bool enabled = true; + if (has_disabled_exts) { + for (size_t i = 0; i < assimp_disable_extensions.get_num_words(); ++i) { + std::string disabled_ext = assimp_disable_extensions.get_word(i); + if (strcmp(sub + 2, disabled_ext.c_str()) == 0) { + enabled = false; + break; + } + } + } + if (enabled) { + *(p++) = ' '; + size_t len = strlen(sub + 2); + memcpy(p, sub + 2, len); + p += len; + } + + sub = strtok(nullptr, ";"); + } + + // Strip first space + ++buffer; + return std::string(buffer, p - buffer); +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz or .gz extension), false otherwise. + */ +bool LoaderFileTypeAssimp:: +supports_compressed() const { + return true; +} + +/** + * + */ +PT(PandaNode) LoaderFileTypeAssimp:: +load_file(const Filename &path, const LoaderOptions &options, + BamCacheRecord *record) const { + + assimp_cat.info() + << "Reading " << path << "\n"; + + AssimpLoader loader; + loader.local_object(); + + if (!loader.read(path)) { + return nullptr; + } + + loader.build_graph(); + return DCAST(PandaNode, loader._root); +} diff --git a/pandatool/src/assimp/loaderFileTypeAssimp.h b/pandatool/src/assimp/loaderFileTypeAssimp.h new file mode 100644 index 00000000..033ab9af --- /dev/null +++ b/pandatool/src/assimp/loaderFileTypeAssimp.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file loaderFileTypeAssimp.h + * @author rdb + * @date 2011-03-29 + */ + +#ifndef LOADERFILETYPEASSIMP_H +#define LOADERFILETYPEASSIMP_H + +#include "config_assimp.h" +#include "loaderFileType.h" + +class AssimpLoader; + +/** + * This defines the Loader interface that uses the Assimp library to load + * various model formats. + */ +class EXPCL_ASSIMP LoaderFileTypeAssimp : public LoaderFileType { +public: + LoaderFileTypeAssimp(); + virtual ~LoaderFileTypeAssimp(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual std::string get_additional_extensions() const; + virtual bool supports_compressed() const; + + virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options, + BamCacheRecord *record) const; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LoaderFileType::init_type(); + register_type(_type_handle, "LoaderFileTypeAssimp", + LoaderFileType::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/assimp/p3assimp_composite1.cxx b/pandatool/src/assimp/p3assimp_composite1.cxx new file mode 100644 index 00000000..bc813b11 --- /dev/null +++ b/pandatool/src/assimp/p3assimp_composite1.cxx @@ -0,0 +1,7 @@ + +#include "config_assimp.cxx" +#include "assimpLoader.cxx" +#include "loaderFileTypeAssimp.cxx" +#include "pandaIOStream.cxx" +#include "pandaIOSystem.cxx" +#include "pandaLogger.cxx" diff --git a/pandatool/src/assimp/pandaIOStream.cxx b/pandatool/src/assimp/pandaIOStream.cxx new file mode 100644 index 00000000..e8d4dc86 --- /dev/null +++ b/pandatool/src/assimp/pandaIOStream.cxx @@ -0,0 +1,107 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaIOStream.cxx + * @author rdb + * @date 2011-03-29 + */ + +#include "pandaIOStream.h" + +using std::ios; + +/** + * + */ +PandaIOStream:: +PandaIOStream(std::istream &stream) : _istream(stream) { +} + +/** + * Returns the size of this file. + */ +size_t PandaIOStream:: +FileSize() const { + _istream.clear(); + std::streampos cur = _istream.tellg(); + _istream.seekg(0, ios::end); + std::streampos end = _istream.tellg(); + _istream.seekg(cur); + return end; +} + +/** + * See fflush. + */ +void PandaIOStream:: +Flush() { + nassertv(false); +} + +/** + * See fread. + */ +size_t PandaIOStream:: +Read(void *buffer, size_t size, size_t count) { + _istream.read((char *)buffer, size * count); + + if (_istream.eof()) { + // Gracefully handle EOF. + _istream.clear(ios::eofbit); + } + + return _istream.gcount() / size; +} + +/** + * See fseek. + */ +aiReturn PandaIOStream:: +Seek(size_t offset, aiOrigin origin) { + switch (origin) { + case aiOrigin_SET: + _istream.seekg(offset, ios::beg); + break; + + case aiOrigin_CUR: + _istream.seekg(offset, ios::cur); + break; + + case aiOrigin_END: + _istream.seekg(offset, ios::end); + break; + + default: + // Keep compiler happy + nassertr(false, AI_FAILURE); + break; + } + + if (_istream.good()) { + return AI_SUCCESS; + } else { + return AI_FAILURE; + } +} + +/** + * See ftell. + */ +size_t PandaIOStream:: +Tell() const { + return _istream.tellg(); +} + +/** + * See fwrite. + */ +size_t PandaIOStream:: +Write(const void *buffer, size_t size, size_t count) { + nassertr(false, 0); + return 0; +} diff --git a/pandatool/src/assimp/pandaIOStream.h b/pandatool/src/assimp/pandaIOStream.h new file mode 100644 index 00000000..18c24b14 --- /dev/null +++ b/pandatool/src/assimp/pandaIOStream.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaIOStream.h + * @author rdb + * @date 2011-03-29 + */ + +#ifndef PANDAIOSTREAM_H +#define PANDAIOSTREAM_H + +#include "config_assimp.h" + +#include + +class PandaIOSystem; + +/** + * Custom implementation of Assimp::IOStream. It simply wraps around an + * istream object, and is unable to write. + */ +class PandaIOStream : public Assimp::IOStream { +public: + PandaIOStream(std::istream &stream); + virtual ~PandaIOStream() {}; + + size_t FileSize() const; + void Flush(); + size_t Read(void *pvBuffer, size_t pSize, size_t pCount); + aiReturn Seek(size_t pOffset, aiOrigin pOrigin); + size_t Tell() const; + size_t Write(const void *buffer, size_t size, size_t count); + +private: + std::istream &_istream; + + friend class PandaIOSystem; +}; + +#endif diff --git a/pandatool/src/assimp/pandaIOSystem.cxx b/pandatool/src/assimp/pandaIOSystem.cxx new file mode 100644 index 00000000..241790f6 --- /dev/null +++ b/pandatool/src/assimp/pandaIOSystem.cxx @@ -0,0 +1,85 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaIOSystem.cxx + * @author rdb + * @date 2011-03-29 + */ + +#include "pandaIOSystem.h" +#include "pandaIOStream.h" + +/** + * Initializes the object with the given VFS, or the global one if none was + * specified. + */ +PandaIOSystem:: +PandaIOSystem(VirtualFileSystem *vfs) : _vfs(vfs) { +} + +/** + * Returns true if the file exists, duh. + */ +bool PandaIOSystem:: +Exists(const char *file) const { + Filename fn = Filename::from_os_specific(file); + return _vfs->exists(fn); +} + +/** + * Closes the indicated file stream. + */ +void PandaIOSystem:: +Close(Assimp::IOStream *file) { + PandaIOStream *pstr = (PandaIOStream*) file; + _vfs->close_read_file(&pstr->_istream); +} + +/** + * Returns true if the two paths point to the same file, false if not. + */ +bool PandaIOSystem:: +ComparePaths(const char *p1, const char *p2) const { + Filename fn1 = Filename::from_os_specific(p1); + Filename fn2 = Filename::from_os_specific(p2); + fn1.make_canonical(); + fn2.make_canonical(); + return fn1 == fn2; +} + +/** + * Returns the path separator for this operating system. + */ +char PandaIOSystem:: +getOsSeparator() const { +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +/** + * Opens the indicated file. + */ +Assimp::IOStream *PandaIOSystem:: +Open(const char *file, const char *mode) { + Filename fn = Filename::from_os_specific(file); + + if (mode[0] == 'r') { + std::istream *stream = _vfs->open_read_file(file, true); + if (stream == nullptr) { + return nullptr; + } + return new PandaIOStream(*stream); + + } else { + nassert_raise("write mode not implemented"); + return nullptr; + } +} diff --git a/pandatool/src/assimp/pandaIOSystem.h b/pandatool/src/assimp/pandaIOSystem.h new file mode 100644 index 00000000..be8ad2dd --- /dev/null +++ b/pandatool/src/assimp/pandaIOSystem.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaIOSystem.h + * @author rdb + * @date 2011-03-29 + */ + +#ifndef PANDAIOSYSTEM_H +#define PANDAIOSYSTEM_H + +#include "config_assimp.h" +#include "virtualFileSystem.h" + +#include + +/** + * Custom implementation of Assimp::IOSystem. + */ +class PandaIOSystem : public Assimp::IOSystem { +public: + PandaIOSystem(VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr()); + virtual ~PandaIOSystem() {}; + + void Close(Assimp::IOStream *file); + bool ComparePaths(const char *p1, const char *p2) const; + bool Exists(const char *file) const; + char getOsSeparator() const; + Assimp::IOStream *Open(const char *file, const char *mode); + +private: + VirtualFileSystem *_vfs; +}; + +#endif diff --git a/pandatool/src/assimp/pandaLogger.cxx b/pandatool/src/assimp/pandaLogger.cxx new file mode 100644 index 00000000..ee0a4132 --- /dev/null +++ b/pandatool/src/assimp/pandaLogger.cxx @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaLogger.cxx + * @author rdb + * @date 2011-05-05 + */ + +#include "pandaLogger.h" + +#include + +PandaLogger *PandaLogger::_ptr = nullptr; + +/** + * Makes sure there's a global PandaLogger object and makes sure that it is + * Assimp's default logger. + */ +void PandaLogger:: +set_default() { + if (_ptr == nullptr) { + _ptr = new PandaLogger; + } + if (_ptr != Assimp::DefaultLogger::get()) { + Assimp::DefaultLogger::set(_ptr); + } +} + +/** + * + */ +void PandaLogger::OnDebug(const char *message) { + if (assimp_cat.is_debug()) { + assimp_cat.debug() << message << "\n"; + } +} + +/** + * + */ +void PandaLogger::OnVerboseDebug(const char *message) { + if (assimp_cat.is_spam()) { + assimp_cat.spam() << message << "\n"; + } +} + +/** + * + */ +void PandaLogger::OnError(const char *message) { + assimp_cat.error() << message << "\n"; +} + +/** + * + */ +void PandaLogger::OnInfo(const char *message) { + assimp_cat.info() << message << "\n"; +} + +/** + * + */ +void PandaLogger::OnWarn(const char *message) { + assimp_cat.warning() << message << "\n"; +} diff --git a/pandatool/src/assimp/pandaLogger.h b/pandatool/src/assimp/pandaLogger.h new file mode 100644 index 00000000..2fdbd446 --- /dev/null +++ b/pandatool/src/assimp/pandaLogger.h @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandaLogger.h + * @author rdb + * @date 2011-05-05 + */ + +#ifndef PANDALOGGER_H +#define PANDALOGGER_H + +#include "config_assimp.h" + +#include + +/** + * Custom implementation of Assimp::Logger. It simply wraps around the + * assimp_cat methods. + */ +class PandaLogger : public Assimp::Logger { +public: + static void set_default(); + +protected: + INLINE bool attachStream(Assimp::LogStream*, unsigned int) { + return false; + }; + INLINE bool detachStream(Assimp::LogStream*, unsigned int) { + return false; + }; + + // Kept for compatibility with Assimp 4.x + INLINE bool detatchStream(Assimp::LogStream*, unsigned int) { + return false; + }; + + void OnDebug(const char *message); + void OnVerboseDebug(const char *message); + void OnError(const char *message); + void OnInfo(const char *message); + void OnWarn(const char *message); + +private: + static PandaLogger *_ptr; +}; + +#endif diff --git a/pandatool/src/bam/CMakeLists.txt b/pandatool/src/bam/CMakeLists.txt new file mode 100644 index 00000000..32f5a6f5 --- /dev/null +++ b/pandatool/src/bam/CMakeLists.txt @@ -0,0 +1,27 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(bam-info bamInfo.cxx bamInfo.h) +target_link_libraries(bam-info p3progbase panda) +install(TARGETS bam-info EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(egg2bam eggToBam.cxx eggToBam.h) + target_link_libraries(egg2bam p3eggbase p3progbase panda) + install(TARGETS egg2bam EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + + if(HAVE_SQUISH) + target_compile_definitions(egg2bam PRIVATE HAVE_SQUISH) + endif() + + add_executable(bam2egg bamToEgg.cxx bamToEgg.h) + target_link_libraries(bam2egg p3converter p3eggbase p3progbase panda) + install(TARGETS bam2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(pts2bam ptsToBam.cxx ptsToBam.h) + target_link_libraries(pts2bam p3progbase pandaegg panda) + install(TARGETS pts2bam EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/bam/bamInfo.cxx b/pandatool/src/bam/bamInfo.cxx new file mode 100644 index 00000000..291d3379 --- /dev/null +++ b/pandatool/src/bam/bamInfo.cxx @@ -0,0 +1,328 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file bamInfo.cxx + * @author drose + * @date 2000-07-02 + */ + +#include "bamInfo.h" + +#include "bamFile.h" +#include "pandaNode.h" +#include "geomNode.h" +#include "texture.h" +#include "recorderHeader.h" +#include "recorderFrame.h" +#include "recorderTable.h" +#include "dcast.h" +#include "pvector.h" +#include "bamCacheRecord.h" +#include "bamCacheIndex.h" + +/** + * + */ +BamInfo:: +BamInfo() { + set_program_brief("describe the contents of .bam files"); + set_program_description + ("This program scans one or more Bam files--Panda's Binary Animation " + "and Models native binary format--and describes their contents."); + + clear_runlines(); + add_runline("[opts] input.bam [input.bam ... ]"); + + add_option + ("ls", "", 0, + "List the scene graph hierarchy in the bam file.", + &BamInfo::dispatch_none, &_ls); + + add_option + ("t", "", 0, + "List explicitly each transition in the hierarchy.", + &BamInfo::dispatch_none, &_verbose_transitions); + + add_option + ("g", "", 0, + "Output verbose information about each Geom in the Bam file.", + &BamInfo::dispatch_none, &_verbose_geoms); + + _num_scene_graphs = 0; +} + + +/** + * + */ +void BamInfo:: +run() { + bool okflag = true; + + Filenames::const_iterator fi; + for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) { + if (!get_info(*fi)) { + okflag = false; + } + } + + if (_num_scene_graphs > 0) { + nout << "\nScene graph statistics:\n"; + _analyzer.write(nout, 2); + } + nout << "\n"; + + if (!okflag) { + // Exit with an error if any of the files was unreadable. + exit(1); + } +} + + +/** + * + */ +bool BamInfo:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the Bam file(s) to read on the command line.\n"; + return false; + } + + ProgramBase::Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + _filenames.push_back(*ai); + } + + return true; +} + + +/** + * Reads a single Bam file and displays its contents. Returns true if + * successful, false on error. + */ +bool BamInfo:: +get_info(const Filename &filename) { + BamFile bam_file; + + if (!bam_file.open_read(filename)) { + nout << "Unable to read.\n"; + return false; + } + + const char *endian = "little-endian"; + if (bam_file.get_file_endian() == BamEnums::BE_bigendian) { + endian = "big-endian"; + } + int float_width = 32; + if (bam_file.get_file_stdfloat_double()) { + float_width = 64; + } + + nout << filename << " : Bam version " << bam_file.get_file_major_ver() + << "." << bam_file.get_file_minor_ver() + << ", " << endian << ", " << float_width << "-bit floats.\n"; + + Objects objects; + TypedWritable *object = bam_file.read_object(); + + if (object != nullptr && + object->is_exact_type(BamCacheRecord::get_class_type())) { + // Here's a special case: if the first object in the file is a + // BamCacheRecord, it's a cache data file; in this case, we output the + // cache record, and then pretend it doesn't exist. + DCAST(BamCacheRecord, object)->write(nout, 2); + nout << "\n"; + object = bam_file.read_object(); + } + + while (object != nullptr || !bam_file.is_eof()) { + if (object != nullptr) { + objects.push_back(object); + } + object = bam_file.read_object(); + } + if (!bam_file.resolve()) { + nout << "Unable to fully resolve file.\n"; + return false; + } + + // We can't close the bam file until we have examined the objects, since + // closing it will decrement reference counts. + + if (objects.size() == 1 && + objects[0]->is_of_type(PandaNode::get_class_type())) { + describe_scene_graph(DCAST(PandaNode, objects[0])); + + } else if (objects.size() == 1 && + objects[0]->is_of_type(Texture::get_class_type())) { + describe_texture(DCAST(Texture, objects[0])); + + } else if (objects.size() == 1 && + objects[0]->is_of_type(BamCacheIndex::get_class_type())) { + describe_cache_index(DCAST(BamCacheIndex, objects[0])); + + } else if (!objects.empty() && objects[0]->is_of_type(RecorderHeader::get_class_type())) { + describe_session(DCAST(RecorderHeader, objects[0]), objects); + + } else { + nout << "file contains " << objects.size() << " objects:\n"; + for (int i = 0; i < (int)objects.size(); i++) { + describe_general_object(objects[i]); + } + } + + return true; +} + + +/** + * Called for Bam files that contain a single scene graph and no other + * objects. This should describe that scene graph in some meaningful way. + */ +void BamInfo:: +describe_scene_graph(PandaNode *node) { + // Parent the node to our own scene graph root, so we can (a) guarantee it + // won't accidentally be deleted before we're done, (b) easily determine the + // bounding volume of the scene, and (c) report statistics on all the bam + // file's scene graphs together when we've finished. + + PT(PandaNode) root = new PandaNode("root"); + root->add_child(node); + _num_scene_graphs++; + + int num_nodes = _analyzer.get_num_nodes(); + _analyzer.add_node(node); + num_nodes = _analyzer.get_num_nodes() - num_nodes; + + nout << " " << num_nodes << " nodes, bounding volume is " + << *root->get_bounds() << "\n"; + + if (_ls || _verbose_geoms || _verbose_transitions) { + list_hierarchy(node, 0); + } +} + +/** + * Called for Bam files that contain a Texture object. + */ +void BamInfo:: +describe_texture(Texture *tex) { + tex->write(nout, 2); +} + +/** + * Called for Bam files that contain a BamCacheIndex object. + */ +void BamInfo:: +describe_cache_index(BamCacheIndex *index) { + index->write(nout, 2); +} + +/** + * Called for Bam files that contain a recorded session table. + */ +void BamInfo:: +describe_session(RecorderHeader *header, const BamInfo::Objects &objects) { + char time_buffer[1024]; + strftime(time_buffer, 1024, "%c", + localtime(&header->_start_time)); + + pset recorders; + double last_timestamp = 0.0; + + for (size_t i = 1; i < objects.size(); i++) { + if (objects[i]->is_of_type(RecorderFrame::get_class_type())) { + RecorderFrame *frame = DCAST(RecorderFrame, objects[i]); + if (frame->_table_changed) { + RecorderTable::Recorders::const_iterator ri; + for (ri = frame->_table->_recorders.begin(); + ri != frame->_table->_recorders.end(); + ++ri) { + recorders.insert((*ri).first); + } + } + last_timestamp = frame->_timestamp; + } + } + + nout << "Session, " << last_timestamp + << " secs, " << objects.size() - 1 << " frames, " + << time_buffer << ".\n" + << "Recorders:"; + for (pset::iterator ni = recorders.begin(); + ni != recorders.end(); + ++ni) { + nout << " " << (*ni); + } + nout << "\n"; +} + +/** + * Called for Bam files that contain multiple objects which may or may not be + * scene graph nodes. This should describe each object in some meaningful + * way. + */ +void BamInfo:: +describe_general_object(TypedWritable *object) { + nassertv(object != nullptr); + nout << " " << object->get_type() << "\n"; +} + +/** + * Outputs the hierarchy and all of the verbose GeomNode information. + */ +void BamInfo:: +list_hierarchy(PandaNode *node, int indent_level) { + indent(nout, indent_level) << *node; + + if (_verbose_transitions) { + nout << "\n"; + if (!node->get_transform()->is_identity()) { + node->get_transform()->write(nout, indent_level); + } + if (!node->get_state()->is_empty()) { + node->get_state()->write(nout, indent_level); + } + if (!node->get_effects()->is_empty()) { + node->get_effects()->write(nout, indent_level); + } + + } else { + if (!node->get_transform()->is_identity()) { + nout << " " << *node->get_transform(); + } + if (!node->get_state()->is_empty()) { + nout << " " << *node->get_state(); + } + if (!node->get_effects()->is_empty()) { + nout << " " << *node->get_effects(); + } + nout << "\n"; + } + + if (_verbose_geoms && node->is_geom_node()) { + GeomNode *geom_node; + DCAST_INTO_V(geom_node, node); + geom_node->write_verbose(nout, indent_level); + } + + int num_children = node->get_num_children(); + for (int i = 0; i < num_children; i++) { + PandaNode *child = node->get_child(i); + list_hierarchy(child, indent_level + 2); + } +} + +int main(int argc, char *argv[]) { + BamInfo prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/bamInfo.h b/pandatool/src/bam/bamInfo.h new file mode 100644 index 00000000..766c6986 --- /dev/null +++ b/pandatool/src/bam/bamInfo.h @@ -0,0 +1,65 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file bamInfo.h + * @author drose + * @date 2000-07-02 + */ + +#ifndef BAMINFO_H +#define BAMINFO_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "filename.h" +#include "sceneGraphAnalyzer.h" + +#include "pvector.h" + +class TypedWritable; +class PandaNode; +class Texture; +class BamCacheIndex; +class RecorderHeader; + +/** + * + */ +class BamInfo : public ProgramBase { +public: + BamInfo(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + typedef pvector Objects; + + bool get_info(const Filename &filename); + void describe_scene_graph(PandaNode *node); + void describe_texture(Texture *tex); + void describe_cache_index(BamCacheIndex *index); + void describe_session(RecorderHeader *header, const Objects &objects); + void describe_general_object(TypedWritable *object); + void list_hierarchy(PandaNode *node, int indent_level); + + typedef pvector Filenames; + Filenames _filenames; + + bool _ls; + bool _verbose_transitions; + bool _verbose_geoms; + + int _num_scene_graphs; + SceneGraphAnalyzer _analyzer; +}; + +#endif diff --git a/pandatool/src/bam/bamToEgg.cxx b/pandatool/src/bam/bamToEgg.cxx new file mode 100644 index 00000000..96aa14e4 --- /dev/null +++ b/pandatool/src/bam/bamToEgg.cxx @@ -0,0 +1,104 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file bamToEgg.cxx + * @author drose + * @date 2001-06-25 + */ + +#include "bamToEgg.h" +#include "save_egg_file.h" +#include "string_utils.h" +#include "bamFile.h" +#include "bamCacheRecord.h" + +/** + * + */ +BamToEgg:: +BamToEgg() : + SomethingToEgg("bam", ".bam") +{ + set_program_brief("convert a native Panda .bam file to an .egg file"); + set_program_description + ("This program converts native Panda bam files to egg. The conversion " + "is somewhat incomplete; running egg2bam followed by bam2egg should not " + "be expected to yield the same egg file you started with."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. By default, this is taken from the Config.prc file, which " + "is currently " + format_string(get_default_coordinate_system()) + "."); + + _coordinate_system = get_default_coordinate_system(); +} + +/** + * + */ +void BamToEgg:: +run() { + BamFile bam_file; + + if (!bam_file.open_read(_input_filename)) { + nout << "Unable to read " << _input_filename << "\n"; + exit(1); + } + + nout << _input_filename << " : Bam version " + << bam_file.get_file_major_ver() << "." + << bam_file.get_file_minor_ver() << "\n"; + + typedef pvector Objects; + Objects objects; + TypedWritable *object = bam_file.read_object(); + + if (object != nullptr && + object->is_exact_type(BamCacheRecord::get_class_type())) { + // Here's a special case: if the first object in the file is a + // BamCacheRecord, it's really a cache data file and not a true bam file; + // but skip over the cache data record and let the user treat it like an + // ordinary bam file. + object = bam_file.read_object(); + } + + while (object != nullptr || !bam_file.is_eof()) { + if (object != nullptr) { + ReferenceCount *ref_ptr = object->as_reference_count(); + if (ref_ptr != nullptr) { + ref_ptr->ref(); + } + objects.push_back(object); + } + object = bam_file.read_object(); + } + bam_file.resolve(); + bam_file.close(); + + _data->set_coordinate_system(_coordinate_system); + + if (objects.size() == 1 && + objects[0]->is_of_type(PandaNode::get_class_type())) { + PandaNode *node = DCAST(PandaNode, objects[0]); + save_egg_data(_data, node); + + } else { + nout << "File does not contain a scene graph.\n"; + exit(1); + } + + write_egg_file(); +} + +int main(int argc, char *argv[]) { + BamToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/bamToEgg.h b/pandatool/src/bam/bamToEgg.h new file mode 100644 index 00000000..f5a29c3f --- /dev/null +++ b/pandatool/src/bam/bamToEgg.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file bamToEgg.h + * @author drose + * @date 2001-06-25 + */ + +#ifndef BAMTOEGG_H +#define BAMTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" + +/** + * This program reads a bam file, for instance as written out from a real-time + * interaction session, and generates a corresponding egg file. + */ +class BamToEgg : public SomethingToEgg { +public: + BamToEgg(); + + void run(); + +private: +}; + +#endif diff --git a/pandatool/src/bam/eggToBam.cxx b/pandatool/src/bam/eggToBam.cxx new file mode 100644 index 00000000..a7e760d2 --- /dev/null +++ b/pandatool/src/bam/eggToBam.cxx @@ -0,0 +1,491 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToBam.cxx + * @author drose + * @date 2000-06-28 + */ + +#include "eggToBam.h" + +#include "config_putil.h" +#include "bamFile.h" +#include "load_egg_file.h" +#include "config_egg2pg.h" +#include "config_gobj.h" +#include "config_chan.h" +#include "pandaNode.h" +#include "geomNode.h" +#include "renderState.h" +#include "textureAttrib.h" +#include "dcast.h" +#include "graphicsPipeSelection.h" +#include "graphicsEngine.h" +#include "graphicsBuffer.h" +#include "graphicsStateGuardian.h" +#include "load_prc_file.h" +#include "windowProperties.h" +#include "frameBufferProperties.h" + +/** + * + */ +EggToBam:: +EggToBam() : + EggToSomething("Bam", ".bam", true, false) +{ + set_program_brief("convert .egg files to .bam files"); + set_program_description + ("This program reads Egg files and outputs Bam files, the binary format " + "suitable for direct loading of animation and models into Panda. Bam " + "files are tied to a particular version of Panda, so should not be " + "considered replacements for egg files, but they tend to be smaller and " + "load much faster than the equivalent egg files."); + + // -f is always in effect for egg2bam. It doesn't make sense to provide it + // as an option to the user. + remove_option("f"); + + add_path_replace_options(); + add_path_store_options(); + + add_option + ("flatten", "flag", 0, + "Specifies whether to flatten the egg hierarchy after it is loaded. " + "If flag is zero, the egg hierarchy will not be flattened, but will " + "instead be written to the bam file exactly as it is. If flag is " + "non-zero, the hierarchy will be flattened so that unnecessary nodes " + "(usually group nodes with only one child) are eliminated. The default " + "if this is not specified is taken from the egg-flatten Config.prc " + "variable.", + &EggToBam::dispatch_int, &_has_egg_flatten, &_egg_flatten); + + add_option + ("combine-geoms", "flag", 0, + "Specifies whether to combine sibling GeomNodes into a common GeomNode " + "when possible. This flag is only respected if flatten, above, is also " + "enabled (or implicitly true from the Config.prc file). The default if " + "this is not specified is taken from the egg-combine-geoms Config.prc " + "variable.", + &EggToBam::dispatch_int, &_has_egg_combine_geoms, &_egg_combine_geoms); + + add_option + ("suppress-hidden", "flag", 0, + "Specifies whether to suppress hidden geometry. If this is nonzero, " + "egg geometry tagged as \"hidden\" will be removed from the final " + "scene graph; otherwise, it will be preserved (but stashed). The " + "default is nonzero, to remove it.", + &EggToBam::dispatch_int, nullptr, &_egg_suppress_hidden); + + add_option + ("ls", "", 0, + "Writes a scene graph listing to standard output after the egg " + "file has been loaded, showing the nodes that will be written out.", + &EggToBam::dispatch_none, &_ls); + + add_option + ("C", "quality", 0, + "Specify the quality level for lossy channel compression. If this " + "is specified, the animation channels will be compressed at this " + "quality level, which is normally an integer value between 0 and 100, " + "inclusive, where higher numbers produce larger files with greater " + "quality. Generally, 95 is the highest useful quality level. Use " + "-NC (described below) to disable channel compression. If neither " + "option is specified, the default comes from the Config.prc file.", + &EggToBam::dispatch_int, &_has_compression_quality, &_compression_quality); + + add_option + ("NC", "", 0, + "Turn off lossy compression of animation channels. Channels will be " + "written exactly as they are, losslessly.", + &EggToBam::dispatch_none, &_compression_off); + + add_option + ("rawtex", "", 0, + "Record texture data directly in the bam file, instead of storing " + "a reference to the texture elsewhere on disk. The textures are " + "stored uncompressed, unless -ctex is also specified. " + "A particular texture that is encoded into " + "multiple different bam files in this way cannot be unified into " + "the same part of texture memory if the different bam files are loaded " + "together. That being said, this can sometimes be a convenient " + "way to ensure the bam file is completely self-contained.", + &EggToBam::dispatch_none, &_tex_rawdata); + + add_option + ("txo", "", 0, + "Rather than writing texture data directly into the bam file, as in " + "-rawtex, create a texture object for each referenced texture. A " + "texture object is a kind of mini-bam file, with a .txo extension, " + "that contains all of the data needed to recreate a texture, including " + "its image contents, filter and wrap settings, and so on. 3-D textures " + "and cube maps can also be represented in a single .txo file. Texture " + "object files, like bam files, are tied to a particular version of " + "Panda.", + &EggToBam::dispatch_none, &_tex_txo); + +#ifdef HAVE_ZLIB + add_option + ("txopz", "", 0, + "In addition to writing texture object files as above, compress each " + "one using pzip to a .txo.pz file. In many cases, this will yield a " + "disk file size comparable to that achieved by png compression. This " + "is an on-disk compression only, and does not affect the amount of " + "RAM or texture memory consumed by the texture when it is loaded.", + &EggToBam::dispatch_none, &_tex_txopz); +#endif // HAVE_ZLIB + + add_option + ("ctex", "", 0, +#ifdef HAVE_SQUISH + "Pre-compress the texture images using the libsquish library, when " + "using -rawtex or -txo. " +#else + "Asks the graphics card to pre-compress the texture images when using " + "-rawtex or -txo. " +#endif // HAVE_SQUISH +#ifdef HAVE_ZLIB + "This is unrelated to the on-disk compression achieved " + "via -txopz (and it may be used in conjunction with that parameter). " +#endif // HAVE_ZLIB + "This will result in a smaller RAM and texture memory footprint for " + "the texture images. The same " + "effect can be achieved at load time by setting compressed-textures in " + "your Config.prc file; but -ctex pre-compresses the " + "textures so that they do not need to be compressed at load time. " +#ifndef HAVE_SQUISH + "Note that, since your Panda is not compiled with the libsquish " + "library, using -ctex will make .txo files that are only guaranteed " + "to load on the particular graphics card that was used to " + "generate them." +#endif // HAVE_SQUISH + , + &EggToBam::dispatch_none, &_tex_ctex); + + add_option + ("mipmap", "", 0, + "Records the pre-generated mipmap levels in the texture object file " + "when using -rawtex or -txo, regardless of the texture filter mode. This " + "will increase the size of the texture object file by about 33%, but " + "it prevents the need to compute the mipmaps at runtime. The default " + "is to record mipmap levels only when the texture uses a mipmap " + "filter mode.", + &EggToBam::dispatch_none, &_tex_mipmap); + + add_option + ("ctexq", "quality", 0, + "Specifies the compression quality to use when performing the " + "texture compression requested by -ctex. This may be one of " + "'default', 'fastest', 'normal', or 'best'. The default is 'best'. " + "Set it to 'default' to use whatever is specified by the Config.prc " + "file. This is a global setting only; individual texture quality " + "settings appearing within the egg file will override this.", + &EggToBam::dispatch_string, nullptr, &_ctex_quality); + + add_option + ("load-display", "display name", 0, + "Specifies the particular display module to load to perform the texture " + "compression requested by -ctex. If this is omitted, the default is " + "taken from the Config.prc file." +#ifdef HAVE_SQUISH + " Since your Panda has libsquish compiled in, this is not necessary; " + "Panda can compress textures without loading a display module." +#endif // HAVE_SQUISH + , + &EggToBam::dispatch_string, nullptr, &_load_display); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting " + _format_name + + " file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is z-up."); + + _force_complete = true; + _egg_flatten = 0; + _egg_combine_geoms = 0; + _egg_suppress_hidden = 1; + _tex_txopz = false; + _ctex_quality = "best"; +} + +/** + * + */ +void EggToBam:: +run() { + if (_has_egg_flatten) { + // If the user specified some -flatten, we need to set the corresponding + // Config.prc variable. + egg_flatten = (_egg_flatten != 0); + } + if (_has_egg_combine_geoms) { + // Ditto with -combine_geoms. + egg_combine_geoms = (_egg_combine_geoms != 0); + } + + // We always set egg_suppress_hidden. + egg_suppress_hidden = _egg_suppress_hidden; + + if (_compression_off) { + // If the user specified -NC, turn off channel compression. + compress_channels = false; + + } else if (_has_compression_quality) { + // Otherwise, if the user specified a compression quality with -C, use + // that quality level. + compress_channels = true; + compress_chan_quality = _compression_quality; + } + + if (_ctex_quality != "default") { + // Override the user's config file with the command-line parameter for + // texture compression. + std::string prc = "texture-quality-level " + _ctex_quality; + load_prc_file_data("prc", prc); + } + + if (!_got_coordinate_system) { + // If the user didn't specify otherwise, ensure the coordinate system is + // Z-up. + _data->set_coordinate_system(CS_zup_right); + } + + PT(PandaNode) root = load_egg_data(_data); + if (root == nullptr) { + nout << "Unable to build scene graph from egg file.\n"; + exit(1); + } + + if (_tex_ctex) { +#ifndef HAVE_SQUISH + if (!make_buffer()) { + nout << "Unable to initialize graphics context; cannot compress textures.\n"; + exit(1); + } +#endif // HAVE_SQUISH + } + + if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) { + collect_textures(root); + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + Texture *tex = (*ti); + tex->get_ram_image(); + bool want_mipmaps = (_tex_mipmap || tex->uses_mipmaps()); + if (want_mipmaps) { + // Generate mipmap levels. + tex->generate_ram_mipmap_images(); + } + + if (_tex_ctex) { +#ifdef HAVE_SQUISH + if (!tex->compress_ram_image()) { + nout << " couldn't compress " << tex->get_name() << "\n"; + } + tex->set_compression(Texture::CM_on); +#else // HAVE_SQUISH + tex->set_keep_ram_image(true); + bool has_mipmap_levels = (tex->get_num_ram_mipmap_images() > 1); + if (!_engine->extract_texture_data(tex, _gsg)) { + nout << " couldn't compress " << tex->get_name() << "\n"; + } + if (!has_mipmap_levels && !want_mipmaps) { + // Make sure we didn't accidentally introduce mipmap levels by + // rendezvousing through the graphics card. + tex->clear_ram_mipmap_images(); + } + tex->set_keep_ram_image(false); +#endif // HAVE_SQUISH + } + + if (_tex_txo || _tex_txopz) { + convert_txo(tex); + } + } + } + + if (_ls) { + root->ls(nout, 0); + } + + // This should be guaranteed because we pass false to the constructor, + // above. + nassertv(has_output_filename()); + + Filename filename = get_output_filename(); + filename.make_dir(); + nout << "Writing " << filename << "\n"; + BamFile bam_file; + if (!bam_file.open_write(filename)) { + nout << "Error in writing.\n"; + exit(1); + } + + if (!bam_file.write_object(root)) { + nout << "Error in writing.\n"; + exit(1); + } +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggToBam:: +handle_args(ProgramBase::Args &args) { + // If the user specified a path store option, we need to set the bam- + // texture-mode Config.prc variable directly to support this (otherwise the + // bam code will do what it wants to do anyway). + if (_tex_rawdata) { + bam_texture_mode = BamFile::BTM_rawdata; + + } else if (_got_path_store) { + bam_texture_mode = BamFile::BTM_unchanged; + + } else { + // Otherwise, the default path store is absolute; then the bam-texture- + // mode can do the appropriate thing to it. + _path_replace->_path_store = PS_absolute; + } + + return EggToSomething::handle_args(args); +} + +/** + * Recursively walks the scene graph, looking for Texture references. + */ +void EggToBam:: +collect_textures(PandaNode *node) { + collect_textures(node->get_state()); + if (node->is_geom_node()) { + GeomNode *geom_node = DCAST(GeomNode, node); + int num_geoms = geom_node->get_num_geoms(); + for (int i = 0; i < num_geoms; ++i) { + collect_textures(geom_node->get_geom_state(i)); + } + } + + PandaNode::Children children = node->get_children(); + int num_children = children.get_num_children(); + for (int i = 0; i < num_children; ++i) { + collect_textures(children.get_child(i)); + } +} + +/** + * Recursively walks the scene graph, looking for Texture references. + */ +void EggToBam:: +collect_textures(const RenderState *state) { + const TextureAttrib *tex_attrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_type())); + if (tex_attrib != nullptr) { + int num_on_stages = tex_attrib->get_num_on_stages(); + for (int i = 0; i < num_on_stages; ++i) { + _textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i))); + } + } +} + +/** + * If the indicated Texture was not already loaded from a txo file, writes it + * to a txo file and updates the Texture object to reference the new file. + */ +void EggToBam:: +convert_txo(Texture *tex) { + if (!tex->get_loaded_from_txo()) { + Filename fullpath = tex->get_fullpath().get_filename_index(0); + if (_tex_txopz) { + fullpath.set_extension("txo.pz"); + // We use this clumsy syntax so that the new extension appears to be two + // separate extensions, .txo followed by .pz, which is what + // Texture::write() expects to find. + fullpath = Filename(fullpath.get_fullpath()); + } else { + fullpath.set_extension("txo"); + } + + if (tex->write(fullpath)) { + nout << " Writing " << fullpath; + if (tex->get_ram_image_compression() != Texture::CM_off) { + nout << " (compressed " << tex->get_ram_image_compression() << ")"; + } + nout << "\n"; + tex->set_loaded_from_txo(); + tex->set_fullpath(fullpath); + tex->clear_alpha_fullpath(); + + Filename filename = tex->get_filename().get_filename_index(0); + if (_tex_txopz) { + filename.set_extension("txo.pz"); + filename = Filename(filename.get_fullpath()); + } else { + filename.set_extension("txo"); + } + + tex->set_filename(filename); + tex->clear_alpha_filename(); + } + } +} + +/** + * Creates a GraphicsBuffer for communicating with the graphics card. + */ +bool EggToBam:: +make_buffer() { + if (!_load_display.empty()) { + // Override the user's config file with the command-line parameter. + std::string prc = "load-display " + _load_display; + load_prc_file_data("prc", prc); + } + + GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr(); + _pipe = selection->make_default_pipe(); + if (_pipe == nullptr) { + nout << "Unable to create graphics pipe.\n"; + return false; + } + + _engine = new GraphicsEngine; + + FrameBufferProperties fbprops = FrameBufferProperties::get_default(); + + // Some graphics drivers can only create single-buffered offscreen buffers. + // So request that. + fbprops.set_back_buffers(0); + + WindowProperties winprops; + winprops.set_size(1, 1); + winprops.set_origin(0, 0); + winprops.set_undecorated(true); + winprops.set_open(true); + winprops.set_z_order(WindowProperties::Z_bottom); + + // We don't care how big the buffer is; we just need it to manifest the GSG. + _buffer = _engine->make_output(_pipe, "buffer", 0, + fbprops, winprops, + GraphicsPipe::BF_fb_props_optional); + _engine->open_windows(); + if (_buffer == nullptr || !_buffer->is_valid()) { + nout << "Unable to create graphics window.\n"; + return false; + } + _gsg = _buffer->get_gsg(); + + return true; +} + + +int main(int argc, char *argv[]) { + EggToBam prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/eggToBam.h b/pandatool/src/bam/eggToBam.h new file mode 100644 index 00000000..1ab262a5 --- /dev/null +++ b/pandatool/src/bam/eggToBam.h @@ -0,0 +1,77 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToBam.h + * @author drose + * @date 2000-06-28 + */ + +#ifndef EGGTOBAM_H +#define EGGTOBAM_H + +#include "pandatoolbase.h" + +#include "eggToSomething.h" +#include "pset.h" +#include "graphicsPipe.h" + +class PandaNode; +class RenderState; +class Texture; +class GraphicsEngine; +class GraphicsStateGuardian; +class GraphicsOutput; + +/** + * + */ +class EggToBam : public EggToSomething { +public: + EggToBam(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + void collect_textures(PandaNode *node); + void collect_textures(const RenderState *state); + void convert_txo(Texture *tex); + + bool make_buffer(); + +private: + typedef pset Textures; + Textures _textures; + + bool _has_egg_flatten; + int _egg_flatten; + bool _has_egg_combine_geoms; + int _egg_combine_geoms; + bool _egg_suppress_hidden; + bool _ls; + bool _has_compression_quality; + int _compression_quality; + bool _compression_off; + bool _tex_rawdata; + bool _tex_txo; + bool _tex_txopz; + bool _tex_ctex; + bool _tex_mipmap; + std::string _ctex_quality; + std::string _load_display; + + // The rest of this is required to support -ctex. + PT(GraphicsPipe) _pipe; + GraphicsStateGuardian *_gsg; + GraphicsEngine *_engine; + GraphicsOutput *_buffer; +}; + +#endif diff --git a/pandatool/src/bam/ptsToBam.cxx b/pandatool/src/bam/ptsToBam.cxx new file mode 100644 index 00000000..0059df86 --- /dev/null +++ b/pandatool/src/bam/ptsToBam.cxx @@ -0,0 +1,238 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ptsToBam.cxx + * @author drose + * @date 2000-06-28 + */ + +#include "ptsToBam.h" + +#include "config_putil.h" +#include "geomPoints.h" +#include "bamFile.h" +#include "pandaNode.h" +#include "geomNode.h" +#include "dcast.h" +#include "string_utils.h" +#include "config_egg2pg.h" + +using std::string; + +/** + * + */ +PtsToBam:: +PtsToBam() : WithOutputFile(true, false, true) +{ + set_program_brief("convert point cloud data into a .bam file"); + set_program_description + ("This program reads a point clound in a pts file and outputs a bam files, " + "suitable for viewing in Panda."); + + clear_runlines(); + add_runline("[opts] input.pts output.bam"); + add_runline("[opts] -o output.bam input.pts"); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting .bam file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file.", + &PtsToBam::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("d", "divisor", 0, + "Decimates the point cloud by the indicated divisor. The number of points\n" + "added is 1/divisor; numbers larger than 1.0 mean correspondingly fewer\n" + "points.", + &PtsToBam::dispatch_double, nullptr, &_decimate_divisor); + + _decimate_divisor = 1.0; +} + +/** + * + */ +void PtsToBam:: +run() { + pifstream pts; + _pts_filename.set_text(); + if (!_pts_filename.open_read(pts)) { + nout << "Cannot open " << _pts_filename << "\n"; + exit(1); + } + + _gnode = new GeomNode(_pts_filename.get_basename()); + + _num_points_expected = 0; + _num_points_found = 0; + _num_points_added = 0; + _decimate_factor = 1.0 / std::max(1.0, _decimate_divisor); + _line_number = 0; + _point_number = 0; + _decimated_point_number = 0.0; + _num_vdatas = 0; + string line; + while (std::getline(pts, line)) { + process_line(line); + } + close_vertex_data(); + + nout << "\nFound " << _num_points_found << " points of " << _num_points_expected << " expected.\n"; + nout << "Generated " << _num_points_added << " points to bam file.\n"; + + // This should be guaranteed because we pass false to the constructor, + // above. + nassertv(has_output_filename()); + + Filename filename = get_output_filename(); + filename.make_dir(); + nout << "Writing " << filename << "\n"; + BamFile bam_file; + if (!bam_file.open_write(filename)) { + nout << "Error in writing.\n"; + exit(1); + } + + if (!bam_file.write_object(_gnode.p())) { + nout << "Error in writing.\n"; + exit(1); + } +} + +/** + * + */ +bool PtsToBam:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the pts file to read on the command line.\n"; + return false; + } + + if (args.size() > 1) { + nout << "Specify only one pts on the command line.\n"; + return false; + } + + _pts_filename = Filename::from_os_specific(args[0]); + + return true; +} + +/** + * Reads a single line from the pts file. + */ +void PtsToBam:: +process_line(const string &line) { + _line_number++; + + if (_line_number % 1000000 == 0) { + std::cerr << "." << std::flush; + } + + if (line.empty() || !isdigit(line[0])) { + return; + } + + if (_line_number == 1) { + // The first line might be just the number of points. + vector_string words; + tokenize(trim(line), words, " \t", true); + if (words.size() == 1) { + string tail; + _num_points_expected = string_to_int(words[0], tail); + nout << "Expecting " << _num_points_expected << " points, will generate " + << (int)(_num_points_expected * _decimate_factor) << "\n"; + return; + } + } + + // Here we might have a point. + _num_points_found++; + _decimated_point_number += _decimate_factor; + int point_number = int(_decimated_point_number); + if (point_number > _point_number) { + _point_number = point_number; + + vector_string words; + tokenize(trim(line), words, " \t", true); + if (words.size() >= 3) { + add_point(words); + } + } +} + +/** + * Adds a point from the pts file. + */ +void PtsToBam:: +add_point(const vector_string &words) { + if (_data == nullptr || _data->get_num_rows() >= egg_max_vertices) { + open_vertex_data(); + } + + string tail; + double x, y, z; + x = string_to_double(words[0], tail); + y = string_to_double(words[1], tail); + z = string_to_double(words[2], tail); + _vertex.add_data3d(x, y, z); + _num_points_added++; +} + +/** + * Creates a new GeomVertexData. + */ +void PtsToBam:: +open_vertex_data() { + if (_data != nullptr) { + close_vertex_data(); + } + CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3(); + _data = new GeomVertexData("pts", format, GeomEnums::UH_static); + _vertex = GeomVertexWriter(_data, "vertex"); +} + +/** + * Closes a previous GeomVertexData and adds it to the scene graph. + */ +void PtsToBam:: +close_vertex_data() { + if (_data == nullptr) { + return; + } + + _num_vdatas++; + nout << "\nGenerating " << _num_points_added << " points in " << _num_vdatas << " GeomVertexDatas\n"; + + PT(Geom) geom = new Geom(_data); + + int num_vertices = _data->get_num_rows(); + int vertices_so_far = 0; + while (num_vertices > 0) { + int this_num_vertices = std::min(num_vertices, (int)egg_max_indices); + PT(GeomPrimitive) points = new GeomPoints(GeomEnums::UH_static); + points->add_consecutive_vertices(vertices_so_far, this_num_vertices); + geom->add_primitive(points); + vertices_so_far += this_num_vertices; + num_vertices -= this_num_vertices; + } + + _gnode->add_geom(geom); + + _data = nullptr; +} + +int main(int argc, char *argv[]) { + PtsToBam prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/bam/ptsToBam.h b/pandatool/src/bam/ptsToBam.h new file mode 100644 index 00000000..20fa76e7 --- /dev/null +++ b/pandatool/src/bam/ptsToBam.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ptsToBam.h + * @author drose + * @date 2000-06-28 + */ + +#ifndef PTSTOBAM_H +#define PTSTOBAM_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "withOutputFile.h" +#include "filename.h" +#include "vector_string.h" +#include "geomVertexData.h" +#include "geomVertexWriter.h" +#include "geomNode.h" + +/** + * + */ +class PtsToBam : public ProgramBase, public WithOutputFile { +public: + PtsToBam(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + void process_line(const std::string &line); + void add_point(const vector_string &words); + + void open_vertex_data(); + void close_vertex_data(); + +private: + Filename _pts_filename; + double _decimate_divisor; + double _decimate_factor; + + int _line_number; + int _point_number; + int _num_points_expected; + int _num_points_found; + int _num_points_added; + int _num_vdatas; + + double _decimated_point_number; + PT(GeomNode) _gnode; + PT(GeomVertexData) _data; + GeomVertexWriter _vertex; +}; + +#endif diff --git a/pandatool/src/converter/CMakeLists.txt b/pandatool/src/converter/CMakeLists.txt new file mode 100644 index 00000000..a793b718 --- /dev/null +++ b/pandatool/src/converter/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3CONVERTER_HEADERS + somethingToEggConverter.h somethingToEggConverter.I + eggToSomethingConverter.h eggToSomethingConverter.I +) + +set(P3CONVERTER_SOURCES + somethingToEggConverter.cxx eggToSomethingConverter.cxx +) + +add_library(p3converter STATIC ${P3CONVERTER_HEADERS} ${P3CONVERTER_SOURCES}) +target_link_libraries(p3converter p3pandatoolbase pandaegg) + +install(TARGETS p3converter + EXPORT ToolsDevel COMPONENT ToolsDevel + DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d + ARCHIVE COMPONENT ToolsDevel) +install(FILES ${P3CONVERTER_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) diff --git a/pandatool/src/converter/eggToSomethingConverter.I b/pandatool/src/converter/eggToSomethingConverter.I new file mode 100644 index 00000000..54320824 --- /dev/null +++ b/pandatool/src/converter/eggToSomethingConverter.I @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToSomethingConverter.I + * @author drose + * @date 2012-09-26 + */ + +/** + * Resets the error flag to the no-error state. had_error() will return false + * until a new error is generated. + */ +INLINE void EggToSomethingConverter:: +clear_error() { + _error = false; +} + +/** + * Returns true if an error was detected during the conversion process, false + * otherwise. + */ +INLINE bool EggToSomethingConverter:: +had_error() const { + return _error; +} + +/** + * Sets the EggData to NULL and makes the converter invalid. + */ +INLINE void EggToSomethingConverter:: +clear_egg_data() { + set_egg_data(nullptr); +} + +/** + * Returns the EggData structure. + */ +INLINE EggData *EggToSomethingConverter:: +get_egg_data() { + return _egg_data; +} + +/** + * Specifies the units that the EggData has already been scaled to. This is + * informational only; if the target file format supports it, this information + * will be written to the header. + */ +void EggToSomethingConverter:: +set_output_units(DistanceUnit output_units) { + _output_units = output_units; +} + +/** + * Returns the value supplied to set_output_units(). + */ +DistanceUnit EggToSomethingConverter:: +get_output_units() const { + return _output_units; +} diff --git a/pandatool/src/converter/eggToSomethingConverter.cxx b/pandatool/src/converter/eggToSomethingConverter.cxx new file mode 100644 index 00000000..0f357883 --- /dev/null +++ b/pandatool/src/converter/eggToSomethingConverter.cxx @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToSomethingConverter.cxx + * @author drose + * @date 2001-04-26 + */ + +#include "eggToSomethingConverter.h" + +#include "eggData.h" + +/** + * + */ +EggToSomethingConverter:: +EggToSomethingConverter() { + _egg_data = nullptr; + _error = false; +} + +/** + * + */ +EggToSomethingConverter:: +EggToSomethingConverter(const EggToSomethingConverter ©) { + _egg_data = nullptr; + _error = false; +} + +/** + * + */ +EggToSomethingConverter:: +~EggToSomethingConverter() { + clear_egg_data(); +} + +/** + * Sets the egg data that will be filled in when convert_file() is called. + * This must be called before convert_file(). + */ +void EggToSomethingConverter:: +set_egg_data(EggData *egg_data) { + _egg_data = egg_data; +} + +/** + * Returns a space-separated list of extension, in addition to the one + * returned by get_extension(), that are recognized by this converter. + */ +std::string EggToSomethingConverter:: +get_additional_extensions() const { + return std::string(); +} + +/** + * Returns true if this file type can transparently save compressed files + * (with a .pz extension), false otherwise. + */ +bool EggToSomethingConverter:: +supports_compressed() const { + return false; +} diff --git a/pandatool/src/converter/eggToSomethingConverter.h b/pandatool/src/converter/eggToSomethingConverter.h new file mode 100644 index 00000000..0e95958c --- /dev/null +++ b/pandatool/src/converter/eggToSomethingConverter.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToSomethingConverter.h + * @author drose + * @date 2012-09-26 + */ + +#ifndef EGGTOSOMETHINGCONVERTER_H +#define EGGTOSOMETHINGCONVERTER_H + +#include "pandatoolbase.h" + +#include "filename.h" +#include "pointerTo.h" +#include "distanceUnit.h" +#include "coordinateSystem.h" + +class EggData; +class EggGroupNode; + +/** + * This is a base class for a family of converter classes that manage a + * conversion from egg format to some other file type. + * + * Classes of this type can be used to implement egg2xxx converter programs, + * as well as LoaderFileTypeXXX run-time savers. + */ +class EggToSomethingConverter { +public: + EggToSomethingConverter(); + EggToSomethingConverter(const EggToSomethingConverter ©); + virtual ~EggToSomethingConverter(); + + virtual EggToSomethingConverter *make_copy()=0; + + INLINE void clear_error(); + INLINE bool had_error() const; + + void set_egg_data(EggData *egg_data); + INLINE void clear_egg_data(); + INLINE EggData *get_egg_data(); + + INLINE void set_output_units(DistanceUnit output_units); + INLINE DistanceUnit get_output_units() const; + INLINE void set_output_coordinate_system(CoordinateSystem output_coordinate_system) const; + INLINE CoordinateSystem get_output_coordinate_system() const; + + virtual std::string get_name() const=0; + virtual std::string get_extension() const=0; + virtual std::string get_additional_extensions() const; + virtual bool supports_compressed() const; + + virtual bool write_file(const Filename &filename)=0; + +protected: + PT(EggData) _egg_data; + DistanceUnit _output_units; + CoordinateSystem _output_coordinate_system; + + bool _error; +}; + +#include "eggToSomethingConverter.I" + +#endif diff --git a/pandatool/src/converter/somethingToEggConverter.I b/pandatool/src/converter/somethingToEggConverter.I new file mode 100644 index 00000000..b3fed0cb --- /dev/null +++ b/pandatool/src/converter/somethingToEggConverter.I @@ -0,0 +1,391 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file somethingToEggConverter.I + * @author drose + * @date 2001-04-26 + */ + +/** + * Resets the error flag to the no-error state. had_error() will return false + * until a new error is generated. + */ +INLINE void SomethingToEggConverter:: +clear_error() { + _error = false; +} + +/** + * Returns true if an error was detected during the conversion process (unless + * _allow_errors is true), false otherwise. + */ +INLINE bool SomethingToEggConverter:: +had_error() const { + return !_allow_errors && (_error || _path_replace->had_error()); +} + +/** + * Replaces the PathReplace object (which specifies how to mangle paths from + * the source to the destination egg file) with a new one. + */ +INLINE void SomethingToEggConverter:: +set_path_replace(PathReplace *path_replace) { + _path_replace = path_replace; +} + +/** + * Returns a pointer to the PathReplace object associated with this converter. + * If the converter is non-const, this returns a non-const pointer, which can + * be adjusted. + */ +INLINE PathReplace *SomethingToEggConverter:: +get_path_replace() { + return _path_replace; +} + +/** + * Returns a pointer to the PathReplace object associated with this converter. + * If the converter is non-const, this returns a non-const pointer, which can + * be adjusted. + */ +INLINE const PathReplace *SomethingToEggConverter:: +get_path_replace() const { + return _path_replace; +} + +/** + * Specifies how source animation will be converted into egg structures. The + * default is AC_none, which means animation tables will be ignored. This is + * only meaningful for converters that understand animation. + */ +INLINE void SomethingToEggConverter:: +set_animation_convert(AnimationConvert animation_convert) { + _animation_convert = animation_convert; +} + +/** + * Returns how source animation will be converted into egg structures. + */ +INLINE AnimationConvert SomethingToEggConverter:: +get_animation_convert() const { + return _animation_convert; +} + +/** + * Specifies the name of the character generated. This name should match + * between all the model and channel egg files for a particular character and + * its associated animations. + */ +INLINE void SomethingToEggConverter:: +set_character_name(const std::string &character_name) { + _character_name = character_name; +} + +/** + * Returns the name of the character generated. See set_character_name(). + */ +INLINE const std::string &SomethingToEggConverter:: +get_character_name() const { + return _character_name; +} + +/** + * Specifies the starting frame of the animation to convert, in the units + * specified by set_input_frame_rate(). If this is unspecified, the starting + * frame is taken from the source, for instance from the first frame of the + * animation slider. + */ +INLINE void SomethingToEggConverter:: +set_start_frame(double start_frame) { + _start_frame = start_frame; + _control_flags |= CF_start_frame; +} + +/** + * Returns true if the starting frame has been explicitly specified via + * set_start_frame(), or false if the starting frame should be implicit based + * on the source. + */ +INLINE bool SomethingToEggConverter:: +has_start_frame() const { + return (_control_flags & CF_start_frame) != 0; +} + +/** + * Returns the value set by a previous call to set_start_frame(). It is an + * error to call this if has_start_frame() returns false. + */ +INLINE double SomethingToEggConverter:: +get_start_frame() const { + nassertr(has_start_frame(), 0.0); + return _start_frame; +} + +/** + * Removes the value previously set by set_start_frame(). + */ +INLINE void SomethingToEggConverter:: +clear_start_frame() { + _start_frame = 0.0; + _control_flags &= ~CF_start_frame; +} + +/** + * Specifies the ending frame of the animation to convert, in the units + * specified by set_input_frame_rate(). If this is unspecified, the ending + * frame is taken from the source, for instance from the last frame of the + * animation slider. + */ +INLINE void SomethingToEggConverter:: +set_end_frame(double end_frame) { + _end_frame = end_frame; + _control_flags |= CF_end_frame; +} + +/** + * Returns true if the ending frame has been explicitly specified via + * set_end_frame(), or false if the ending frame should be implicit based on + * the source. + */ +INLINE bool SomethingToEggConverter:: +has_end_frame() const { + return (_control_flags & CF_end_frame) != 0; +} + +/** + * Returns the value set by a previous call to set_end_frame(). It is an + * error to call this if has_end_frame() returns false. + */ +INLINE double SomethingToEggConverter:: +get_end_frame() const { + nassertr(has_end_frame(), 0.0); + return _end_frame; +} + +/** + * Removes the value previously set by set_end_frame(). + */ +INLINE void SomethingToEggConverter:: +clear_end_frame() { + _end_frame = 0.0; + _control_flags &= ~CF_end_frame; +} + +/** + * Specifies the increment between frames to extract. This is the amount to + * increment the time slider (in units of internal_frame_rate) between + * extracting each frame. If this is not specified, the default is taken from + * the animation package, or 1.0 if the animation package does not specified a + * frame increment. + */ +INLINE void SomethingToEggConverter:: +set_frame_inc(double frame_inc) { + _frame_inc = frame_inc; + _control_flags |= CF_frame_inc; +} + +/** + * Returns true if the frame increment has been explicitly specified via + * set_frame_inc(), or false if the ending frame should be implicit based on + * the source. + */ +INLINE bool SomethingToEggConverter:: +has_frame_inc() const { + return (_control_flags & CF_frame_inc) != 0; +} + +/** + * Returns the value set by a previous call to set_frame_inc(). It is an + * error to call this if has_frame_inc() returns false. + */ +INLINE double SomethingToEggConverter:: +get_frame_inc() const { + nassertr(has_frame_inc(), 0.0); + return _frame_inc; +} + +/** + * Removes the value previously set by set_frame_inc(). + */ +INLINE void SomethingToEggConverter:: +clear_frame_inc() { + _frame_inc = 0.0; + _control_flags &= ~CF_frame_inc; +} + +/** + * Specifies the frame of animation to represent the neutral pose of the + * model. + */ +INLINE void SomethingToEggConverter:: +set_neutral_frame(double neutral_frame) { + _neutral_frame = neutral_frame; + _control_flags |= CF_neutral_frame; +} + +/** + * Returns true if the neutral frame has been explicitly specified via + * set_neutral_frame(), or false otherwise. + */ +INLINE bool SomethingToEggConverter:: +has_neutral_frame() const { + return (_control_flags & CF_neutral_frame) != 0; +} + +/** + * Returns the value set by a previous call to set_neutral_frame(). It is an + * error to call this if has_neutral_frame() returns false. + */ +INLINE double SomethingToEggConverter:: +get_neutral_frame() const { + nassertr(has_neutral_frame(), 0.0); + return _neutral_frame; +} + +/** + * Removes the value previously set by set_neutral_frame(). + */ +INLINE void SomethingToEggConverter:: +clear_neutral_frame() { + _neutral_frame = 0.0; + _control_flags &= ~CF_neutral_frame; +} + +/** + * Specifies the number of frames per second that is represented by the + * "frame" unit in the animation package. If this is omitted, it is taken + * from whatever the file header indicates. Some animation packages do not + * encode a frame rate, in which case the default if this is omitted is the + * same as the output frame rate. + */ +INLINE void SomethingToEggConverter:: +set_input_frame_rate(double input_frame_rate) { + _input_frame_rate = input_frame_rate; + _control_flags |= CF_input_frame_rate; +} + +/** + * Returns true if the frame rate has been explicitly specified via + * set_input_frame_rate(), or false otherwise. + */ +INLINE bool SomethingToEggConverter:: +has_input_frame_rate() const { + return (_control_flags & CF_input_frame_rate) != 0; +} + +/** + * Returns the value set by a previous call to set_input_frame_rate(). It is + * an error to call this if has_input_frame_rate() returns false. + */ +INLINE double SomethingToEggConverter:: +get_input_frame_rate() const { + nassertr(has_input_frame_rate(), 0.0); + return _input_frame_rate; +} + +/** + * Removes the value previously set by set_input_frame_rate(). + */ +INLINE void SomethingToEggConverter:: +clear_input_frame_rate() { + _input_frame_rate = 0.0; + _control_flags &= ~CF_input_frame_rate; +} + +/** + * Specifies the number of frames per second that the resulting animation + * should be played at. If this is omitted, it is taken to be the same as the + * input frame rate. + */ +INLINE void SomethingToEggConverter:: +set_output_frame_rate(double output_frame_rate) { + _output_frame_rate = output_frame_rate; + _control_flags |= CF_output_frame_rate; +} + +/** + * Returns true if the frame rate has been explicitly specified via + * set_output_frame_rate(), or false otherwise. + */ +INLINE bool SomethingToEggConverter:: +has_output_frame_rate() const { + return (_control_flags & CF_output_frame_rate) != 0; +} + +/** + * Returns the value set by a previous call to set_output_frame_rate(). It is + * an error to call this if has_output_frame_rate() returns false. + */ +INLINE double SomethingToEggConverter:: +get_output_frame_rate() const { + nassertr(has_output_frame_rate(), 0.0); + return _output_frame_rate; +} + +/** + * Removes the value previously set by set_output_frame_rate(). + */ +INLINE void SomethingToEggConverter:: +clear_output_frame_rate() { + _output_frame_rate = 0.0; + _control_flags &= ~CF_output_frame_rate; +} + +/** + * Returns the default frame rate if nothing is specified for input_frame_rate + * or output_frame_rate, and the animation package does not have an implicit + * frame rate. + */ +INLINE double SomethingToEggConverter:: +get_default_frame_rate() { + return 24.0; +} + +/** + * Sets the merge_externals flag. When this is true, external references + * within the source file are read in and merged directly; otherwise, only a + * reference to a similarly-named egg file is inserted. + */ +INLINE void SomethingToEggConverter:: +set_merge_externals(bool merge_externals) { + _merge_externals = merge_externals; +} + +/** + * Returns the current state of the merge_externals flag. See + * set_merge_externals(). + */ +INLINE bool SomethingToEggConverter:: +get_merge_externals() const { + return _merge_externals; +} + +/** + * Sets the EggData to NULL and makes the converter invalid. + */ +INLINE void SomethingToEggConverter:: +clear_egg_data() { + set_egg_data(nullptr); +} + +/** + * Returns the EggData structure. + */ +INLINE EggData *SomethingToEggConverter:: +get_egg_data() { + return _egg_data; +} + +/** + * Converts the indicated model filename to a relative or absolute or whatever + * filename, according to _path_replace. + */ +INLINE Filename SomethingToEggConverter:: +convert_model_path(const Filename &orig_filename) { + return _path_replace->convert_path(orig_filename); +} diff --git a/pandatool/src/converter/somethingToEggConverter.cxx b/pandatool/src/converter/somethingToEggConverter.cxx new file mode 100644 index 00000000..8911021a --- /dev/null +++ b/pandatool/src/converter/somethingToEggConverter.cxx @@ -0,0 +1,165 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file somethingToEggConverter.cxx + * @author drose + * @date 2001-04-26 + */ + +#include "somethingToEggConverter.h" + +#include "eggData.h" +#include "eggExternalReference.h" + +/** + * + */ +SomethingToEggConverter:: +SomethingToEggConverter() { + _allow_errors = false; + _path_replace = new PathReplace; + _path_replace->_path_store = PS_absolute; + _animation_convert = AC_none; + _start_frame = 0.0; + _end_frame = 0.0; + _frame_inc = 0.0; + _neutral_frame = 0.0; + _input_frame_rate = 0.0; + _output_frame_rate = 0.0; + _control_flags = 0; + _merge_externals = false; + _egg_data = nullptr; + _error = false; +} + +/** + * + */ +SomethingToEggConverter:: +SomethingToEggConverter(const SomethingToEggConverter ©) : + _allow_errors(copy._allow_errors), + _path_replace(copy._path_replace), + _merge_externals(copy._merge_externals) +{ + _egg_data = nullptr; + _error = false; +} + +/** + * + */ +SomethingToEggConverter:: +~SomethingToEggConverter() { + clear_egg_data(); +} + +/** + * Sets the egg data that will be filled in when convert_file() is called. + * This must be called before convert_file(). + */ +void SomethingToEggConverter:: +set_egg_data(EggData *egg_data) { + _egg_data = egg_data; +} + +/** + * Returns a space-separated list of extension, in addition to the one + * returned by get_extension(), that are recognized by this converter. + */ +std::string SomethingToEggConverter:: +get_additional_extensions() const { + return std::string(); +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool SomethingToEggConverter:: +supports_compressed() const { + return false; +} + +/** + * Returns true if this converter can directly convert the model type to + * internal Panda memory structures, given the indicated options, or false + * otherwise. If this returns true, then convert_to_node() may be called to + * perform the conversion, which may be faster than calling convert_file() if + * the ultimate goal is a PandaNode anyway. + */ +bool SomethingToEggConverter:: +supports_convert_to_node(const LoaderOptions &options) const { + return false; +} + +/** + * This may be called after convert_file() has been called and returned true, + * indicating a successful conversion. It will return the distance units + * represented by the converted egg file, if known, or DU_invalid if not + * known. + */ +DistanceUnit SomethingToEggConverter:: +get_input_units() { + return DU_invalid; +} + +/** + * Reads the input file and directly produces a ready-to-render model file as + * a PandaNode. Returns NULL on failure, or if it is not supported. (This + * functionality is not supported by all converter types; see + * supports_convert_to_node()). + */ +PT(PandaNode) SomethingToEggConverter:: +convert_to_node(const LoaderOptions &options, const Filename &filename) { + return nullptr; +} + +/** + * Handles an external reference in the source file. If the merge_externals + * flag is true (see set_merge_externals()), this causes the named file to be + * read in and converted, and the converted egg geometry is parented to + * egg_parent. Otherwise, only a reference to a similarly named egg file is + * parented to egg_parent. + * + * The parameters orig_filename and searchpath are as those passed to + * convert_model_path(). + * + * Returns true on success, false on failure. + */ +bool SomethingToEggConverter:: +handle_external_reference(EggGroupNode *egg_parent, + const Filename &ref_filename) { + if (_merge_externals) { + SomethingToEggConverter *ext = make_copy(); + PT(EggData) egg_data = new EggData; + egg_data->set_coordinate_system(get_egg_data()->get_coordinate_system()); + ext->set_egg_data(egg_data); + + if (!ext->convert_file(ref_filename)) { + delete ext; + nout << "Unable to read external reference: " << ref_filename << "\n"; + _error = true; + return false; + } + + egg_parent->steal_children(*egg_data); + delete ext; + return true; + + } else { + // If we're installing external references instead of reading them, we + // should make it into an egg filename. + Filename filename = ref_filename; + filename.set_extension("egg"); + + EggExternalReference *egg_ref = new EggExternalReference("", filename); + egg_parent->add_child(egg_ref); + } + + return true; +} diff --git a/pandatool/src/converter/somethingToEggConverter.h b/pandatool/src/converter/somethingToEggConverter.h new file mode 100644 index 00000000..16d16df5 --- /dev/null +++ b/pandatool/src/converter/somethingToEggConverter.h @@ -0,0 +1,148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file somethingToEggConverter.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef SOMETHINGTOEGGCONVERTER_H +#define SOMETHINGTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "filename.h" +#include "config_putil.h" // for get_model_path() +#include "animationConvert.h" +#include "pathReplace.h" +#include "pointerTo.h" +#include "distanceUnit.h" +#include "pandaNode.h" + +class EggData; +class EggGroupNode; +class LoaderOptions; + +/** + * This is a base class for a family of converter classes that manage a + * conversion from some file type to egg format. + * + * Classes of this type can be used to implement xxx2egg converter programs, + * as well as LoaderFileTypeXXX run-time loaders. + */ +class SomethingToEggConverter { +public: + SomethingToEggConverter(); + SomethingToEggConverter(const SomethingToEggConverter ©); + virtual ~SomethingToEggConverter(); + + virtual SomethingToEggConverter *make_copy()=0; + + INLINE void clear_error(); + INLINE bool had_error() const; + + INLINE void set_path_replace(PathReplace *path_replace); + INLINE PathReplace *get_path_replace(); + INLINE const PathReplace *get_path_replace() const; + + // These methods dealing with animation and frame rate are only relevant to + // converter types that understand animation. + INLINE void set_animation_convert(AnimationConvert animation_convert); + INLINE AnimationConvert get_animation_convert() const; + + INLINE void set_character_name(const std::string &character_name); + INLINE const std::string &get_character_name() const; + + INLINE void set_start_frame(double start_frame); + INLINE bool has_start_frame() const; + INLINE double get_start_frame() const; + INLINE void clear_start_frame(); + + INLINE void set_end_frame(double end_frame); + INLINE bool has_end_frame() const; + INLINE double get_end_frame() const; + INLINE void clear_end_frame(); + + INLINE void set_frame_inc(double frame_inc); + INLINE bool has_frame_inc() const; + INLINE double get_frame_inc() const; + INLINE void clear_frame_inc(); + + INLINE void set_neutral_frame(double neutral_frame); + INLINE bool has_neutral_frame() const; + INLINE double get_neutral_frame() const; + INLINE void clear_neutral_frame(); + + INLINE void set_input_frame_rate(double input_frame_rate); + INLINE bool has_input_frame_rate() const; + INLINE double get_input_frame_rate() const; + INLINE void clear_input_frame_rate(); + + INLINE void set_output_frame_rate(double output_frame_rate); + INLINE bool has_output_frame_rate() const; + INLINE double get_output_frame_rate() const; + INLINE void clear_output_frame_rate(); + + INLINE static double get_default_frame_rate(); + + INLINE void set_merge_externals(bool merge_externals); + INLINE bool get_merge_externals() const; + + void set_egg_data(EggData *egg_data); + INLINE void clear_egg_data(); + INLINE EggData *get_egg_data(); + + virtual std::string get_name() const=0; + virtual std::string get_extension() const=0; + virtual std::string get_additional_extensions() const; + virtual bool supports_compressed() const; + virtual bool supports_convert_to_node(const LoaderOptions &options) const; + + virtual bool convert_file(const Filename &filename)=0; + virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename); + virtual DistanceUnit get_input_units(); + + bool handle_external_reference(EggGroupNode *egg_parent, + const Filename &ref_filename); + + INLINE Filename convert_model_path(const Filename &orig_filename); + + // Set this true to treat errors as warnings and generate output anyway. + bool _allow_errors; + +protected: + PT(PathReplace) _path_replace; + + AnimationConvert _animation_convert; + std::string _character_name; + double _start_frame; + double _end_frame; + double _frame_inc; + double _neutral_frame; + double _input_frame_rate; // frames per second + double _output_frame_rate; // frames per second + enum ControlFlags { + CF_start_frame = 0x0001, + CF_end_frame = 0x0002, + CF_frame_inc = 0x0004, + CF_neutral_frame = 0x0008, + CF_input_frame_rate = 0x0010, + CF_output_frame_rate = 0x0020, + }; + int _control_flags; + + bool _merge_externals; + + PT(EggData) _egg_data; + + bool _error; +}; + +#include "somethingToEggConverter.I" + +#endif diff --git a/pandatool/src/daeegg/CMakeLists.txt b/pandatool/src/daeegg/CMakeLists.txt new file mode 100644 index 00000000..07b6320d --- /dev/null +++ b/pandatool/src/daeegg/CMakeLists.txt @@ -0,0 +1,26 @@ +if(NOT HAVE_EGG OR NOT HAVE_FCOLLADA) + return() +endif() + +set(P3DAEEGG_HEADERS + config_daeegg.h + daeCharacter.h + daeMaterials.h + daeToEggConverter.h + fcollada_utils.h + pre_fcollada_include.h +) + +set(P3DAEEGG_SOURCES + config_daeegg.cxx + daeCharacter.cxx + daeMaterials.cxx + daeToEggConverter.cxx +) + +add_library(p3daeegg STATIC ${P3DAEEGG_HEADERS} ${P3DAEEGG_SOURCES}) +target_link_libraries(p3daeegg p3eggbase + PKG::FCOLLADA) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/daeegg/config_daeegg.cxx b/pandatool/src/daeegg/config_daeegg.cxx new file mode 100644 index 00000000..2b363155 --- /dev/null +++ b/pandatool/src/daeegg/config_daeegg.cxx @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_daeegg.cxx + * @author rdb + * @date 2008-10-30 + */ + +#include "config_daeegg.h" +#include "daeCharacter.h" +#include "daeMaterials.h" + +#include "dconfig.h" + +Configure(config_daeegg); +NotifyCategoryDef(daeegg, ""); + +ConfigureFn(config_daeegg) { + init_libdaeegg(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libdaeegg() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + DaeCharacter::init_type(); + DaeMaterials::init_type(); +} diff --git a/pandatool/src/daeegg/config_daeegg.h b/pandatool/src/daeegg/config_daeegg.h new file mode 100644 index 00000000..8f60ed73 --- /dev/null +++ b/pandatool/src/daeegg/config_daeegg.h @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_daeegg.h + * @author rdb + * @date 2008-10-30 + */ + +#ifndef CONFIG_DAEEGG_H +#define CONFIG_DAEEGG_H + +#include "pandatoolbase.h" +#include "notifyCategoryProxy.h" + +NotifyCategoryDeclNoExport(daeegg); + +extern void init_libdaeegg(); + +#endif diff --git a/pandatool/src/daeegg/daeCharacter.cxx b/pandatool/src/daeegg/daeCharacter.cxx new file mode 100644 index 00000000..57c048fc --- /dev/null +++ b/pandatool/src/daeegg/daeCharacter.cxx @@ -0,0 +1,312 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeCharacter.cxx + * @author rdb + * @date 2008-11-24 + */ + +#include "daeCharacter.h" +#include "config_daeegg.h" +#include "fcollada_utils.h" +#include "pt_EggVertex.h" +#include "eggXfmSAnim.h" +#include "daeToEggConverter.h" +#include "daeMaterials.h" + +#include "eggExternalReference.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +TypeHandle DaeCharacter::_type_handle; + +/** + * + */ +DaeCharacter:: +DaeCharacter(EggGroup *node_group, const FCDControllerInstance *instance) : + _node_group(node_group), + _skin_mesh(nullptr), + _instance(instance), + _bind_shape_mat(LMatrix4d::ident_mat()), + _name(node_group->get_name()), + _skin_controller(nullptr) { + + // If it's a skin controller, add the controller joints. + const FCDController *controller = (const FCDController *)instance->GetEntity(); + if (controller == nullptr) { + return; + } + _skin_mesh = controller->GetBaseGeometry()->GetMesh(); + + if (controller->IsSkin()) { + _skin_controller = controller->GetSkinController(); + _bind_shape_mat = DAEToEggConverter::convert_matrix(_skin_controller->GetBindShapeTransform()); + } +} + +/** + * Binds the joints to the character. This means changing them to the bind + * pose. It is necessary to call this before process_skin_geometry. + * + * Returns the root group. + */ +void DaeCharacter:: +bind_joints(JointMap &joint_map) { + _joints.clear(); + + size_t num_joints = _skin_controller->GetJointCount(); + _joints.reserve(num_joints); + + // Record the bind pose for each joint. + for (size_t j = 0; j < num_joints; ++j) { + const FCDSkinControllerJoint *skin_joint = _skin_controller->GetJoint(j); + std::string sid = FROM_FSTRING(skin_joint->GetId()); + LMatrix4d bind_pose; + bind_pose.invert_from(DAEToEggConverter::convert_matrix( + skin_joint->GetBindPoseInverse())); + + // Check that we already encountered this joint during traversal. + JointMap::iterator ji = joint_map.find(sid); + if (ji != joint_map.end()) { + Joint &joint = ji->second; + + if (joint._character != nullptr) { + // In some cases, though, multiple controllers share the same joints. + // We can't support this without duplicating the joint structure, so + // we check if the bind poses are the same. + if (!joint._bind_pose.almost_equal(bind_pose, 0.0001)) { + // Ugh. What else could we do? + daeegg_cat.error() + << "Multiple controllers share joint with sid " << sid + << ", with different bind poses.\n"; + } + } else { + // Mark the joint as being controlled by this character. + joint._bind_pose = bind_pose; + joint._character = this; + } + + _joints.push_back(joint); + } else { + daeegg_cat.warning() + << "Unknown joint sid being referenced: '" << sid << "'\n"; + + // We still have to add a dummy joint or the index will be off. + _joints.push_back(Joint(nullptr, nullptr)); + } + } +} + +/** + * Traverses through the character hierarchy in order to bind the mesh to the + * character. This involves reorienting the joints to match the bind pose. + * + * It is important that this is called only once. + */ +void DaeCharacter:: +adjust_joints(FCDSceneNode *node, const JointMap &joint_map, + const LMatrix4d &transform) { + + LMatrix4d this_transform = transform; + + if (node->IsJoint()) { + std::string sid = FROM_FSTRING(node->GetSubId()); + + JointMap::const_iterator ji = joint_map.find(sid); + if (ji != joint_map.end()) { + const Joint &joint = ji->second; + + // Panda needs the joints to be in bind pose. Not fun! We copy the + // joint transform to the default pose, though, so that Panda will + // restore the joint transformation after binding. + + if (joint._character == this) { + LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat * + invert(transform); + // LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat * + // joint._group->get_parent()->get_node_frame_inv(); + + this_transform = bind_pose * this_transform; + joint._group->set_default_pose(*joint._group); + joint._group->set_transform3d(bind_pose); + + /* + PT(EggGroup) sphere = new EggGroup; + sphere->add_uniform_scale(0.1); + sphere->set_group_type(EggGroup::GT_instance); + sphere->add_child(new EggExternalReference("", "jack.egg")); + joint._group->add_child(sphere); + */ + } + } + } else { + // this_transform = DAEToEggConverter::convert_matrix(node->ToMatrix()); + } + + // Loop through the children joints + for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) { + // if (node->GetChild(ch)->IsJoint()) { + adjust_joints(node->GetChild(ch), joint_map, this_transform); + // } + } +} + +/** + * Adds the influences for the given vertex. + */ +void DaeCharacter:: +influence_vertex(int index, EggVertex *vertex) { + const FCDSkinControllerVertex *influence = _skin_controller->GetVertexInfluence(index); + + for (size_t pa = 0; pa < influence->GetPairCount(); ++pa) { + const FCDJointWeightPair* jwpair = influence->GetPair(pa); + + if (jwpair->jointIndex >= 0 && jwpair->jointIndex < (int)_joints.size()) { + EggGroup *joint = _joints[jwpair->jointIndex]._group.p(); + if (joint != nullptr) { + joint->ref_vertex(vertex, jwpair->weight); + } + } else { + daeegg_cat.error() + << "Invalid joint index: " << jwpair->jointIndex << "\n"; + } + } +} + +/** + * Collects all animation keys of animations applied to this character. + */ +void DaeCharacter:: +collect_keys(pset &keys) { +#if FCOLLADA_VERSION < 0x00030005 + FCDSceneNodeList roots = _instance->FindSkeletonNodes(); +#else + FCDSceneNodeList roots; + _instance->FindSkeletonNodes(roots); +#endif + + for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) { + r_collect_keys(*it, keys); + } +} + +/** + * Collects all animation keys found for the given node tree. + */ +void DaeCharacter:: +r_collect_keys(FCDSceneNode* node, pset &keys) { + FCDAnimatedList animateds; + + // Collect all the animation curves + for (size_t t = 0; t < node->GetTransformCount(); ++t) { + FCDTransform *transform = node->GetTransform(t); + FCDAnimated *animated = transform->GetAnimated(); + + if (animated != nullptr) { + const FCDAnimationCurveListList &all_curves = animated->GetCurves(); + + for (size_t ci = 0; ci < all_curves.size(); ++ci) { + const FCDAnimationCurveTrackList &curves = all_curves[ci]; + if (curves.empty()) { + continue; + } + + size_t num_keys = curves.front()->GetKeyCount(); + const FCDAnimationKey **curve_keys = curves.front()->GetKeys(); + + for (size_t c = 0; c < num_keys; ++c) { + keys.insert(curve_keys[c]->input); + } + } + } + } +} + +/** + * Processes a joint node and its transforms. + */ +void DaeCharacter:: +build_table(EggTable *parent, FCDSceneNode* node, const pset &keys) { + nassertv(node != nullptr); + + if (!node->IsJoint()) { + for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) { + build_table(parent, node->GetChild(ch), keys); + } + return; + } + + std::string node_id = FROM_FSTRING(node->GetDaeId()); + PT(EggTable) table = new EggTable(node_id); + table->set_table_type(EggTable::TT_table); + parent->add_child(table); + + PT(EggXfmSAnim) xform = new EggXfmSAnim("xform"); + table->add_child(xform); + + // Generate the sampled animation and loop through the matrices + FCDAnimatedList animateds; + + // Collect all the animation curves + for (size_t t = 0; t < node->GetTransformCount(); ++t) { + FCDTransform *transform = node->GetTransform(t); + FCDAnimated *animated = transform->GetAnimated(); + if (animated != nullptr) { + if (animated->HasCurve()) { + animateds.push_back(animated); + } + } + } + + // Sample the scene node transform + float last_key; + float timing_total = 0; + pset::const_iterator ki; + for (ki = keys.begin(); ki != keys.end(); ++ki) { + for (FCDAnimatedList::iterator it = animateds.begin(); it != animateds.end(); ++it) { + // Sample each animated, which changes the transform values directly + (*it)->Evaluate(*ki); + } + + if (ki != keys.begin()) { + timing_total += (*ki - last_key); + } + last_key = *ki; + + // Retrieve the new transform matrix for the COLLADA scene node + FMMatrix44 fmat = node->ToMatrix(); + + // Work around issue in buggy exporters (like ColladaMax) + if (IS_NEARLY_ZERO(fmat[3][3])) { + fmat[3][3] = 1; + } + + xform->add_data(DAEToEggConverter::convert_matrix(fmat)); + } + + // Quantize the FPS, otherwise Panda complains about FPS mismatches. + float fps = cfloor(((keys.size() - 1) / timing_total) * 100 + 0.5f) * 0.01f; + xform->set_fps(fps); + + // Loop through the children joints + for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) { + // if (node->GetChild(ch)->IsJoint()) { + build_table(table, node->GetChild(ch), keys); + // } + } +} diff --git a/pandatool/src/daeegg/daeCharacter.h b/pandatool/src/daeegg/daeCharacter.h new file mode 100644 index 00000000..e384c221 --- /dev/null +++ b/pandatool/src/daeegg/daeCharacter.h @@ -0,0 +1,95 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeCharacter.h + * @author rdb + * @date 2008-11-24 + */ + +#ifndef DAECHARACTER_H +#define DAECHARACTER_H + +#include "pandatoolbase.h" +#include "typedReferenceCount.h" +#include "typeHandle.h" +#include "eggTable.h" +#include "epvector.h" + +#include "pre_fcollada_include.h" +#include +#include +#include +#include +#include + +class DAEToEggConverter; + +/** + * Class representing an animated character. + */ +class DaeCharacter : public TypedReferenceCount { +public: + DaeCharacter(EggGroup *node_group, const FCDControllerInstance* controller_instance); + + struct Joint { + INLINE Joint(EggGroup *group, const FCDSceneNode *scene_node) : + _bind_pose(LMatrix4d::ident_mat()), + _group(group), + _scene_node(scene_node), + _character(nullptr) {} + + LMatrix4d _bind_pose; + PT(EggGroup) _group; + const FCDSceneNode *_scene_node; + DaeCharacter *_character; + }; + typedef epvector Joints; + typedef pmap JointMap; + + void bind_joints(JointMap &joint_map); + void adjust_joints(FCDSceneNode *node, const JointMap &joint_map, + const LMatrix4d &transform = LMatrix4d::ident_mat()); + + void influence_vertex(int index, EggVertex *vertex); + + void collect_keys(pset &keys); + void r_collect_keys(FCDSceneNode *node, pset &keys); + + void build_table(EggTable *parent, FCDSceneNode* node, const pset &keys); + +public: + PT(EggGroup) _node_group; + const FCDGeometryMesh *_skin_mesh; + const FCDControllerInstance *_instance; + LMatrix4d _bind_shape_mat; + +private: + std::string _name; + const FCDSkinController *_skin_controller; + Joints _joints; + JointMap _bound_joints; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "DaeCharacter", + TypedReferenceCount::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/daeegg/daeMaterials.cxx b/pandatool/src/daeegg/daeMaterials.cxx new file mode 100644 index 00000000..ac221344 --- /dev/null +++ b/pandatool/src/daeegg/daeMaterials.cxx @@ -0,0 +1,451 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeMaterials.cxx + * @author rdb + * @date 2008-10-03 + */ + +#include "daeMaterials.h" +#include "config_daeegg.h" +#include "fcollada_utils.h" + +#include +#include +#include +#include +#include +#include + +#include "filename.h" +#include "string_utils.h" + +using std::endl; +using std::string; + +TypeHandle DaeMaterials::_type_handle; + +// luminance function, based on the ISOCIE color standards see ITU-R +// Recommendation BT.709-4 +#define luminance(c) ((c[0] * 0.212671 + c[1] * 0.715160 + c[2] * 0.072169)) + +/** + * + */ +DaeMaterials:: +DaeMaterials(const FCDGeometryInstance* geometry_instance) { + for (size_t mi = 0; mi < geometry_instance->GetMaterialInstanceCount(); ++mi) { + add_material_instance(geometry_instance->GetMaterialInstance(mi)); + } +} + +/** + * Adds a material instance. Normally automatically done by constructor. + */ +void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) { + nassertv(instance != nullptr); + const string semantic (FROM_FSTRING(instance->GetSemantic())); + if (_materials.count(semantic) > 0) { + daeegg_cat.warning() << "Ignoring duplicate material with semantic " << semantic << endl; + return; + } + _materials[semantic] = new DaeMaterial(); + + // Load in the uvsets + for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) { + const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib); + assert(mivib != nullptr); + PT(DaeVertexInputBinding) bvi = new DaeVertexInputBinding(); + bvi->_input_set = mivib->inputSet; +#if FCOLLADA_VERSION >= 0x00030005 + bvi->_input_semantic = mivib->GetInputSemantic(); + bvi->_semantic = *mivib->semantic; +#else + bvi->_input_semantic = mivib->inputSemantic; + bvi->_semantic = FROM_FSTRING(mivib->semantic); +#endif + _materials[semantic]->_uvsets.push_back(bvi); + } + + // Handle the material stuff + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl; + } + PT_EggMaterial egg_material = new EggMaterial(semantic); + pvector egg_textures; + const FCDEffect* effect = instance->GetMaterial()->GetEffect(); + if (effect == nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Ignoring material (semantic: " << semantic << ") without assigned effect" << endl; + } + } else { + // Grab the common profile effect + const FCDEffectStandard* effect_common = (FCDEffectStandard *)effect->FindProfile(FUDaeProfileType::COMMON); + if (effect_common == nullptr) { + daeegg_cat.info() << "Ignoring effect referenced by material with semantic " << semantic + << " because it has no common profile" << endl; + } else { + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Processing effect, material semantic is " << semantic << endl; + } + // Set the material parameters + egg_material->set_amb(TO_COLOR(effect_common->GetAmbientColor())); + // We already process transparency using blend modes LVecBase4 diffuse = + // TO_COLOR(effect_common->GetDiffuseColor()); + // diffuse.set_w(diffuse.get_w() * (1.0f - + // effect_common->GetOpacity())); egg_material->set_diff(diffuse); + egg_material->set_diff(TO_COLOR(effect_common->GetDiffuseColor())); + egg_material->set_emit(TO_COLOR(effect_common->GetEmissionColor()) * effect_common->GetEmissionFactor()); + egg_material->set_shininess(effect_common->GetShininess()); + egg_material->set_spec(TO_COLOR(effect_common->GetSpecularColor())); + // Now try to load in the textures + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::DIFFUSE, EggTexture::ET_modulate); + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::BUMP, EggTexture::ET_normal); + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR, EggTexture::ET_modulate_gloss); + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR_LEVEL, EggTexture::ET_gloss); + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::TRANSPARENT, EggTexture::ET_unspecified, EggTexture::F_alpha); + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::EMISSION, EggTexture::ET_add); +#if FCOLLADA_VERSION < 0x00030005 + process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::OPACITY, EggTexture::ET_unspecified, EggTexture::F_alpha); +#endif + // Now, calculate the color blend stuff. + _materials[semantic]->_blend = convert_blend(effect_common->GetTransparencyMode(), + TO_COLOR(effect_common->GetTranslucencyColor()), + effect_common->GetTranslucencyFactor()); + } + // Find an tag to support some extra stuff from extensions + process_extra(semantic, effect->GetExtra()); + } + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Found " << egg_textures.size() << " textures in material" << endl; + } + _materials[semantic]->_egg_material = egg_material; +} + +/** + * Processes the given texture bucket and gives the textures in it the given + * envtype and format. + */ +void DaeMaterials:: +process_texture_bucket(const string semantic, const FCDEffectStandard* effect_common, FUDaeTextureChannel::Channel bucket, EggTexture::EnvType envtype, EggTexture::Format format) { + for (size_t tx = 0; tx < effect_common->GetTextureCount(bucket); ++tx) { + const FCDImage* image = effect_common->GetTexture(bucket, tx)->GetImage(); + if (image == nullptr) { + daeegg_cat.warning() << "Texture references a nonexisting image!" << endl; + } else { + const FCDEffectParameterSampler* sampler = effect_common->GetTexture(bucket, tx)->GetSampler(); + // FCollada only supplies absolute paths. We need to grab the document + // location ourselves and make the image path absolute. + Filename texpath; + if (image->GetDocument()) { + Filename docpath = Filename::from_os_specific(FROM_FSTRING(image->GetDocument()->GetFileUrl())); + docpath.make_canonical(); + texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename())); + texpath.make_canonical(); + texpath.make_relative_to(docpath.get_dirname(), true); + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Found texture with path " << texpath << endl; + } + } else { + // Never mind. + texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename())); + } + PT_EggTexture egg_texture = new EggTexture(FROM_FSTRING(image->GetDaeId()), texpath.to_os_generic()); + // Find a set of UV coordinates + const FCDEffectParameterInt* uvset = effect_common->GetTexture(bucket, tx)->GetSet(); + if (uvset != nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Texture has uv name '" << FROM_FSTRING(uvset->GetSemantic()) << "'\n"; + } + string uvset_semantic (FROM_FSTRING(uvset->GetSemantic())); + + // Only set the UV name if this UV set actually exists. + for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) { + if (_materials[semantic]->_uvsets[i]->_semantic == uvset_semantic) { + egg_texture->set_uv_name(uvset_semantic); + break; + } + } + } + // Apply sampler stuff + if (sampler != nullptr) { + egg_texture->set_texture_type(convert_texture_type(sampler->GetSamplerType())); + egg_texture->set_wrap_u(convert_wrap_mode(sampler->GetWrapS())); + if (sampler->GetSamplerType() != FCDEffectParameterSampler::SAMPLER1D) { + egg_texture->set_wrap_v(convert_wrap_mode(sampler->GetWrapT())); + } + if (sampler->GetSamplerType() == FCDEffectParameterSampler::SAMPLER3D) { + egg_texture->set_wrap_w(convert_wrap_mode(sampler->GetWrapP())); + } + egg_texture->set_minfilter(convert_filter_type(sampler->GetMinFilter())); + egg_texture->set_magfilter(convert_filter_type(sampler->GetMagFilter())); + if (envtype != EggTexture::ET_unspecified) { + egg_texture->set_env_type(envtype); + } + if (format != EggTexture::F_unspecified) { + egg_texture->set_format(format); + } + } + _materials[semantic]->_egg_textures.push_back(egg_texture); + } + } +} + +/** + * Processes the extra data in the given tag. If the given element is + * NULL, it just silently returns. + */ +void DaeMaterials:: +process_extra(const string semantic, const FCDExtra* extra) { + if (extra == nullptr) return; + const FCDEType* etype = extra->GetDefaultType(); + if (etype == nullptr) return; + for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) { + const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided"); + if (enode != nullptr) { + string content = trim(enode->GetContent()); + if (content == "1" || content == "true") { + _materials[semantic]->_double_sided = true; + } else if (content == "0" || content == "false") { + _materials[semantic]->_double_sided = false; + } else { + daeegg_cat.warning() << "Expected tag to be either 1 or 0, found '" << content << "' instead" << endl; + } + } + } +} + +/** + * Applies the stuff to the given EggPrimitive. + */ +void DaeMaterials:: +apply_to_primitive(const string semantic, const PT(EggPrimitive) to) { + if (_materials.count(semantic) > 0) { + to->set_material(_materials[semantic]->_egg_material); + for (pvector::iterator it = _materials[semantic]->_egg_textures.begin(); it != _materials[semantic]->_egg_textures.end(); ++it) { + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Applying texture " << (*it)->get_name() << " from material with semantic " << semantic << endl; + } + to->add_texture(*it); + } + to->set_bface_flag(_materials[semantic]->_double_sided); + } +} + +/** + * Applies the colorblend stuff to the given EggGroup. + */ +void DaeMaterials:: +apply_to_group(const string semantic, const PT(EggGroup) to, bool invert_transparency) { + if (_materials.count(semantic) > 0) { + PT(DaeBlendSettings) blend = _materials[semantic]->_blend; + if (blend && blend->_enabled) { + to->set_blend_mode(EggGroup::BM_add); + to->set_blend_color(blend->_color); + if (invert_transparency) { + to->set_blend_operand_a(blend->_operand_b); + to->set_blend_operand_b(blend->_operand_a); + } else { + to->set_blend_operand_a(blend->_operand_a); + to->set_blend_operand_b(blend->_operand_b); + } + } else if (blend && invert_transparency) { + to->set_blend_mode(EggGroup::BM_add); + to->set_blend_color(blend->_color); + to->set_blend_operand_a(blend->_operand_b); + to->set_blend_operand_b(blend->_operand_a); + } + } +} + +/** + * Returns the semantic of the uvset with the specified input set, or an empty + * string if the given material has no input set. + */ +const string DaeMaterials:: +get_uvset_name(const string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set) { + if (_materials.count(semantic) > 0) { + if (input_set == -1 && _materials[semantic]->_uvsets.size() == 1) { + return _materials[semantic]->_uvsets[0]->_semantic; + } else { + for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) { + if (_materials[semantic]->_uvsets[i]->_input_set == input_set && + _materials[semantic]->_uvsets[i]->_input_semantic == input_semantic) { + return _materials[semantic]->_uvsets[i]->_semantic; + } + } + // If we can't find it, let's look again, but don't care for the + // input_semantic this time. The reason for this is that some tools + // export textangents and texbinormals bound to a uvset with input + // semantic TEXCOORD. + for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) { + if (_materials[semantic]->_uvsets[i]->_input_set == input_set) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Using uv set with non-matching input semantic " << _materials[semantic]->_uvsets[i]->_semantic << "\n"; + } + return _materials[semantic]->_uvsets[i]->_semantic; + } + } + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "No uv set binding found for input set " << input_set << "\n"; + } + } + } + return ""; +} + +/** + * Converts an FCollada sampler type to the EggTexture texture type + * equivalent. + */ +EggTexture::TextureType DaeMaterials:: +convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type) { + switch (orig_type) { + case FCDEffectParameterSampler::SAMPLER1D: + return EggTexture::TT_1d_texture; + case FCDEffectParameterSampler::SAMPLER2D: + return EggTexture::TT_2d_texture; + case FCDEffectParameterSampler::SAMPLER3D: + return EggTexture::TT_3d_texture; + case FCDEffectParameterSampler::SAMPLERCUBE: + return EggTexture::TT_cube_map; + default: + daeegg_cat.warning() << "Invalid sampler type found" << endl; + } + return EggTexture::TT_unspecified; +} + +/** + * Converts an FCollada wrap mode to the EggTexture wrap mode equivalent. + */ +EggTexture::WrapMode DaeMaterials:: +convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode) { + switch (orig_mode) { + case FUDaeTextureWrapMode::NONE: + // FIXME: this shouldnt be unspecified + return EggTexture::WM_unspecified; + case FUDaeTextureWrapMode::WRAP: + return EggTexture::WM_repeat; + case FUDaeTextureWrapMode::MIRROR: + return EggTexture::WM_mirror; + case FUDaeTextureWrapMode::CLAMP: + return EggTexture::WM_clamp; + case FUDaeTextureWrapMode::BORDER: + return EggTexture::WM_border_color; + case FUDaeTextureWrapMode::UNKNOWN: + return EggTexture::WM_unspecified; + default: + daeegg_cat.warning() << "Invalid wrap mode found: " << FUDaeTextureWrapMode::ToString(orig_mode) << endl; + } + return EggTexture::WM_unspecified; +} + +/** + * Converts an FCollada filter function to the EggTexture wrap type + * equivalent. + */ +EggTexture::FilterType DaeMaterials:: +convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type) { + switch (orig_type) { + case FUDaeTextureFilterFunction::NONE: + // FIXME: this shouldnt be unspecified + return EggTexture::FT_unspecified; + case FUDaeTextureFilterFunction::NEAREST: + return EggTexture::FT_nearest; + case FUDaeTextureFilterFunction::LINEAR: + return EggTexture::FT_linear; + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_NEAREST: + return EggTexture::FT_nearest_mipmap_nearest; + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST: + return EggTexture::FT_linear_mipmap_nearest; + case FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR: + return EggTexture::FT_nearest_mipmap_linear; + case FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR: + return EggTexture::FT_linear_mipmap_linear; + case FUDaeTextureFilterFunction::UNKNOWN: + return EggTexture::FT_unspecified; + default: + daeegg_cat.warning() << "Unknown filter type found: " << FUDaeTextureFilterFunction::ToString(orig_type) << endl; + } + return EggTexture::FT_unspecified; +} + +/** + * Converts collada blend attribs to Panda's equivalents. + */ +PT(DaeMaterials::DaeBlendSettings) DaeMaterials:: +convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparent, double transparency) { + // Create the DaeBlendSettings and fill it with some defaults. + PT(DaeBlendSettings) blend = new DaeBlendSettings(); + blend->_enabled = true; + blend->_color = LColor::zero(); + blend->_operand_a = EggGroup::BO_unspecified; + blend->_operand_b = EggGroup::BO_unspecified; + + // First fill in the color value. + if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) { + double value = transparent[3] * transparency; + blend->_color = LColor(value, value, value, value); + } else if (mode == FCDEffectStandard::RGB_ZERO) {//|| mode == FCDEffectStandard::RGB_ONE) { + blend->_color = transparent * transparency; + blend->_color[3] = luminance(blend->_color); + } else { + daeegg_cat.error() << "Unknown opaque type found!" << endl; + blend->_enabled = false; + return blend; + } + + // Now figure out the operands. + if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) { + blend->_operand_a = EggGroup::BO_one_minus_constant_color; + blend->_operand_b = EggGroup::BO_constant_color; + } else if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::RGB_ONE) { + blend->_operand_a = EggGroup::BO_constant_color; + blend->_operand_b = EggGroup::BO_one_minus_constant_color; + } else { + daeegg_cat.error() << "Unknown opaque type found!" << endl; + blend->_enabled = false; + return blend; + } + + // See if we can optimize out the color. + if (blend->_operand_a == EggGroup::BO_constant_color) { + if (blend->_color == LColor::zero()) { + blend->_operand_a = EggGroup::BO_zero; + } else if (blend->_color == LColor(1, 1, 1, 1)) { + blend->_operand_a = EggGroup::BO_one; + } + } + if (blend->_operand_b == EggGroup::BO_constant_color) { + if (blend->_color == LColor::zero()) { + blend->_operand_b = EggGroup::BO_zero; + } else if (blend->_color == LColor(1, 1, 1, 1)) { + blend->_operand_b = EggGroup::BO_one; + } + } + if (blend->_operand_a == EggGroup::BO_one_minus_constant_color) { + if (blend->_color == LColor::zero()) { + blend->_operand_a = EggGroup::BO_one; + } else if (blend->_color == LColor(1, 1, 1, 1)) { + blend->_operand_a = EggGroup::BO_zero; + } + } + if (blend->_operand_b == EggGroup::BO_one_minus_constant_color) { + if (blend->_color == LColor::zero()) { + blend->_operand_b = EggGroup::BO_one; + } else if (blend->_color == LColor(1, 1, 1, 1)) { + blend->_operand_b = EggGroup::BO_zero; + } + } + + // See if we can entirely disable the blend. + if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) { + blend->_enabled = false; + } + return blend; +} diff --git a/pandatool/src/daeegg/daeMaterials.h b/pandatool/src/daeegg/daeMaterials.h new file mode 100644 index 00000000..08cfed03 --- /dev/null +++ b/pandatool/src/daeegg/daeMaterials.h @@ -0,0 +1,101 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeMaterials.h + * @author rdb + * @date 2008-10-03 + */ + +#ifndef DAEMATERIALS_H +#define DAEMATERIALS_H + +#include "pandatoolbase.h" +#include "eggMaterial.h" +#include "eggTexture.h" +#include "eggPrimitive.h" +#include "eggGroup.h" +#include "pointerTo.h" +#include "pt_EggTexture.h" +#include "pt_EggMaterial.h" + +#include "pre_fcollada_include.h" +#include +#include +#include +#include +#include +#include + +/** + * This class is seperated from the converter file because otherwise it would + * get too big and needlessly complicated. + */ +class DaeMaterials : public TypedReferenceCount { +public: + DaeMaterials(const FCDGeometryInstance* geometry_instance); + virtual ~DaeMaterials() {}; + + void add_material_instance(const FCDMaterialInstance* instance); + void apply_to_primitive(const std::string semantic, const PT(EggPrimitive) to); + void apply_to_group(const std::string semantic, const PT(EggGroup) to, bool invert_transparency=false); + const std::string get_uvset_name(const std::string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set); + + static EggTexture::TextureType convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type); + static EggTexture::WrapMode convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode); + static EggTexture::FilterType convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type); + +private: + // Holds stuff for color blend attribs. + struct DaeBlendSettings : public ReferenceCount { + bool _enabled; + LColor _color; + EggGroup::BlendOperand _operand_a; + EggGroup::BlendOperand _operand_b; + }; + + // Holds information to bind texcoord inputs to textures. + struct DaeVertexInputBinding : public ReferenceCount { + int32 _input_set; + FUDaeGeometryInput::Semantic _input_semantic; + std::string _semantic; + }; + + // Holds stuff for an individual material. + struct DaeMaterial : public ReferenceCount { + pvector _egg_textures; + PT_EggMaterial _egg_material; + bool _double_sided; + pvector _uvsets; + PT(DaeBlendSettings) _blend; + }; + + void process_texture_bucket(const std::string semantic, const FCDEffectStandard* effect_common, FUDaeTextureChannel::Channel bucket, EggTexture::EnvType envtype = EggTexture::ET_unspecified, EggTexture::Format format = EggTexture::F_unspecified); + void process_extra(const std::string semantic, const FCDExtra* extra); + static PT(DaeBlendSettings) convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparent, double transparency); + + pmap _materials; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "DaeMaterials", + TypedReferenceCount::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/daeegg/daeToEggConverter.cxx b/pandatool/src/daeegg/daeToEggConverter.cxx new file mode 100644 index 00000000..95b146de --- /dev/null +++ b/pandatool/src/daeegg/daeToEggConverter.cxx @@ -0,0 +1,851 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeToEggConverter.cxx + * @author rdb + * @date 2008-05-08 + */ + +#include "daeToEggConverter.h" +#include "fcollada_utils.h" +#include "config_daeegg.h" +#include "daeCharacter.h" +#include "dcast.h" +#include "string_utils.h" +#include "eggData.h" +#include "eggPrimitive.h" +#include "eggLine.h" +#include "eggPolygon.h" +#include "eggTriangleFan.h" +#include "eggTriangleStrip.h" +#include "eggPoint.h" +#include "eggXfmSAnim.h" +#include "eggSAnimData.h" +#include "pt_EggVertex.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if FCOLLADA_VERSION >= 0x00030005 + #include +#endif + +using std::endl; +using std::string; + +/** + * + */ +DAEToEggConverter:: +DAEToEggConverter() { + _unit_name = "meter"; + _unit_meters = 1.0; + _document = nullptr; + _table = nullptr; + _error_handler = nullptr; + _invert_transparency = false; +} + +/** + * + */ +DAEToEggConverter:: +DAEToEggConverter(const DAEToEggConverter ©) : + SomethingToEggConverter(copy) +{ +} + +/** + * + */ +DAEToEggConverter:: +~DAEToEggConverter() { + delete _error_handler; +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *DAEToEggConverter:: +make_copy() { + return new DAEToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +string DAEToEggConverter:: +get_name() const { + return "COLLADA"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +string DAEToEggConverter:: +get_extension() const { + return "dae"; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + */ +bool DAEToEggConverter:: +convert_file(const Filename &filename) { + // Reset stuff + clear_error(); + _joints.clear(); + if (_error_handler == nullptr) { + _error_handler = new FUErrorSimpleHandler; + } + + // The default coordinate system is Y-up + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_yup_right); + } + + // Read the file + FCollada::Initialize(); + _document = FCollada::LoadDocument(filename.to_os_specific().c_str()); + if (_document == nullptr) { + daeegg_cat.error() << "Failed to load document: " << _error_handler->GetErrorString() << endl; + FCollada::Release(); + return false; + } + // Make sure the file uses consistent coordinate system and length + if (_document->GetAsset() != nullptr) { + FCDocumentTools::StandardizeUpAxisAndLength(_document); + } + + // Process the scene + process_asset(); + PT(EggGroup) scene_group; + string model_name = _character_name; + + FCDSceneNode* visual_scene = _document->GetVisualSceneInstance(); + if (visual_scene != nullptr) { + if (model_name.empty()) { + // By lack of anything better... + model_name = FROM_FSTRING(visual_scene->GetName()); + } + scene_group = new EggGroup(model_name); + _egg_data->add_child(scene_group); + + for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) { + process_node(scene_group, visual_scene->GetChild(ch)); + } + } else { + daeegg_cat.warning() + << "No visual scene instance found in COLLADA document.\n"; + } + + // Now process the characters. This depends on information from collected + // joints, which is why it's done in a second step. + if (get_animation_convert() != AC_none) { + Characters::iterator it; + DaeCharacter *character; + for (it = _characters.begin(); it != _characters.end(); ++it) { + character = *it; + if (get_animation_convert() != AC_chan) { + character->bind_joints(_joints); + + const FCDGeometryMesh *mesh = character->_skin_mesh; + + if (mesh != nullptr) { + PT(DaeMaterials) materials = new DaeMaterials(character->_instance); + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Processing mesh for controller\n"; + } + process_mesh(character->_node_group, mesh, materials, character); + } + } + } + + // Put the joints in bind pose. + for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) { + character->adjust_joints(visual_scene->GetChild(ch), _joints, LMatrix4d::ident_mat()); + } + + if (scene_group != nullptr) { + // Mark the scene as character. + if (get_animation_convert() == AC_chan) { + _egg_data->remove_child(scene_group); + } else { + scene_group->set_dart_type(EggGroup::DT_default); + } + } + + if (get_animation_convert() != AC_model) { + _table = new EggTable(); + _table->set_table_type(EggTable::TT_table); + _egg_data->add_child(_table); + + PT(EggTable) bundle = new EggTable(model_name); + bundle->set_table_type(EggTable::TT_bundle); + _table->add_child(bundle); + + PT(EggTable) skeleton = new EggTable(""); + skeleton->set_table_type(EggTable::TT_table); + bundle->add_child(skeleton); + + pset keys; + + Characters::iterator it; + DaeCharacter *character; + for (it = _characters.begin(); it != _characters.end(); ++it) { + character = *it; + + // Collect key frame timings. + if (get_animation_convert() == AC_both || + get_animation_convert() == AC_chan) { + character->collect_keys(keys); + } + } + + if (_frame_inc != 0.0) { + // A frame increment was given, this means that we have to sample the + // animation. + float start, end; + if (_end_frame != _start_frame) { + start = _start_frame; + end = _end_frame; + } else { + // No range was given. Infer the frame range from the keys. + start = *keys.begin(); + end = *keys.rbegin(); + } + keys.clear(); + + for (float t = start; t <= end; t += _frame_inc) { + keys.insert(t); + } + } else { + // No sampling parameters given; not necessarily a failure, since the + // animation may already be sampled. We use the key frames as + // animation frames. + if (_end_frame != 0.0) { + // An end frame was given, chop off all keys after that. + float end = _end_frame; + pset::iterator ki; + for (ki = keys.begin(); ki != keys.end(); ++ki) { + if (*ki > end && !IS_THRESHOLD_EQUAL(*ki, end, 0.001)) { + keys.erase(ki, keys.end()); + break; + } + } + } + if (_start_frame != 0.0) { + // A start frame was given, chop off all keys before that. + float start = _start_frame; + pset::iterator ki; + for (ki = keys.begin(); ki != keys.end(); ++ki) { + if (*ki > start && !IS_THRESHOLD_EQUAL(*ki, start, 0.001)) { + keys.erase(keys.begin(), ki); + break; + } + } + } + + // Check that this does indeed look like a sampled animation; if not, + // issue an appropriate warning. + pset::const_iterator ki = keys.begin(); + if (ki != keys.end()) { + float last = *ki; + float diff = 0; + + for (++ki; ki != keys.end(); ++ki) { + if (diff != 0 && !IS_THRESHOLD_EQUAL((*ki - last), diff, 0.001)) { + daeegg_cat.error() + << "This does not appear to be a sampled animation.\n" + << "Specify the -sf, -ef and -if options to indicate how the " + << "animations should be sampled.\n"; + break; + } + diff = (*ki - last); + last = *ki; + } + } + } + + // It doesn't really matter which character we grab for this as it'll + // iterate over the whole graph right now anyway. + for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) { + character->build_table(skeleton, visual_scene->GetChild(ch), keys); + } + } + } + + // Clean up and return + SAFE_DELETE(visual_scene); + SAFE_DELETE(_document); + FCollada::Release(); + return true; +} + +/** + * This may be called after convert_file() has been called and returned true, + * indicating a successful conversion. It will return the distance units + * represented by the converted egg file, if known, or DU_invalid if not + * known. + */ +DistanceUnit DAEToEggConverter:: +get_input_units() { + if (IS_NEARLY_EQUAL(_unit_meters, 0.001)) { + return DU_millimeters; + } + if (IS_NEARLY_EQUAL(_unit_meters, 0.01)) { + return DU_centimeters; + } + if (IS_NEARLY_EQUAL(_unit_meters, 1.0)) { + return DU_meters; + } + if (IS_NEARLY_EQUAL(_unit_meters, 1000.0)) { + return DU_kilometers; + } + if (IS_NEARLY_EQUAL(_unit_meters, 3.0 * 12.0 * 0.0254)) { + return DU_yards; + } + if (IS_NEARLY_EQUAL(_unit_meters, 12.0 * 0.0254)) { + return DU_feet; + } + if (IS_NEARLY_EQUAL(_unit_meters, 0.0254)) { + return DU_inches; + } + if (IS_NEARLY_EQUAL(_unit_meters, 1852.0)) { + return DU_nautical_miles; + } + if (IS_NEARLY_EQUAL(_unit_meters, 5280.0 * 12.0 * 0.0254)) { + return DU_statute_miles; + } + + // Whatever. + return DU_invalid; +} + +void DAEToEggConverter:: +process_asset() { + const FCDAsset *asset = _document->GetAsset(); + if (_document->GetAsset() == nullptr) { + return; + } + + _unit_name = FROM_FSTRING(asset->GetUnitName()); + _unit_meters = asset->GetUnitConversionFactor(); + + // Read out the coordinate system + FMVector3 up_axis = asset->GetUpAxis(); + + if (up_axis == FMVector3(0, 1, 0)) { + _egg_data->set_coordinate_system(CS_yup_right); + + } else if (up_axis == FMVector3(0, 0, 1)) { + _egg_data->set_coordinate_system(CS_zup_right); + + } else { + _egg_data->set_coordinate_system(CS_invalid); + daeegg_cat.warning() << "Unrecognized coordinate system!\n"; + } +} + +// Process the node. If forced is true, it will even process it if its known +// to be a skeleton root. +void DAEToEggConverter:: +process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) { + nassertv(node != nullptr); + string node_id = FROM_FSTRING(node->GetDaeId()); + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl; + } + + // Create an egg group for this node + PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetDaeId())); + process_extra(node_group, node->GetExtra()); + parent->add_child(node_group); + + // Check if its a joint + if (node->IsJoint()) { + string sid = FROM_FSTRING(node->GetSubId()); + node_group->set_group_type(EggGroup::GT_joint); + + if (!_joints.insert(DaeCharacter::JointMap::value_type(sid, + DaeCharacter::Joint(node_group, node))).second) { + daeegg_cat.error() + << "Joint with sid " << sid << " occurs more than once!\n"; + } + } + + // Loop through the transforms and apply them (in reverse order) + for (size_t tr = node->GetTransformCount(); tr > 0; --tr) { + apply_transform(node_group, node->GetTransform(tr - 1)); + } + // node_group->set_transform3d(convert_matrix(node->ToMatrix())); + + // Loop through the instances and process them + for (size_t in = 0; in < node->GetInstanceCount(); ++in) { + process_instance(node_group, node->GetInstance(in)); + } + + // Loop through the children and recursively process them + for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) { + process_node(DCAST(EggGroupNode, node_group), node->GetChild(ch)); + } + + // Loop through any possible scene node instances and process those, too. + for (size_t in = 0; in < node->GetInstanceCount(); ++in) { + const FCDEntity *entity = node->GetInstance(in)->GetEntity(); + if (entity && entity->GetType() == FCDEntity::SCENE_NODE) { + process_node(node_group, (const FCDSceneNode*) entity); + } + } +} + +void DAEToEggConverter:: +process_instance(EggGroup *parent, const FCDEntityInstance* instance) { + nassertv(instance != nullptr); + nassertv(instance->GetEntity() != nullptr); + // Check what kind of instance this is + switch (instance->GetType()) { + case FCDEntityInstance::GEOMETRY: + { + if (get_animation_convert() != AC_chan) { + const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity(); + assert(geometry != nullptr); + if (geometry->IsMesh()) { + // Now, handle the mesh. + process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance)); + } + if (geometry->IsSpline()) { + process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast (geometry->GetSpline())); + } + } + } + break; + + case FCDEntityInstance::CONTROLLER: + // Add the dart tag and process the controller instance + // parent->set_dart_type(EggGroup::DT_default); + process_controller(parent, (const FCDControllerInstance*) instance); + break; + + case FCDEntityInstance::MATERIAL: + // We don't process this directly, handled per-geometry instead. + break; + + case FCDEntityInstance::SIMPLE: + { + // Grab the entity and check its type. + const FCDEntity* entity = instance->GetEntity(); + if (entity->GetType() != FCDEntity::SCENE_NODE) { + daeegg_cat.warning() << "Unsupported entity type found" << endl; + } + } + break; + + default: + daeegg_cat.warning() << "Unsupported instance type found" << endl; + } +} + +// Processes the given mesh. +void DAEToEggConverter:: +process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh, + DaeMaterials *materials, DaeCharacter *character) { + + nassertv(mesh != nullptr); + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Processing mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << endl; + } + + // Create the egg stuff to hold this mesh + PT(EggGroup) mesh_group = new EggGroup(FROM_FSTRING(mesh->GetDaeId())); + parent->add_child(mesh_group); + PT(EggVertexPool) mesh_pool = new EggVertexPool(FROM_FSTRING(mesh->GetDaeId())); + mesh_group->add_child(mesh_pool); + + // First retrieve the vertex source + if (mesh->GetSourceCount() == 0) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl; + } + return; + } + const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION); + if (vsource == nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no source for POSITION data" << endl; + } + return; + } + + // Loop through the polygon groups and add them + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has " << mesh->GetPolygonsCount() << " polygon groups" << endl; + } + if (mesh->GetPolygonsCount() == 0) return; + + // This is an array of pointers, I know. But since they are refcounted, I + // don't have a better idea. + PT(EggGroup) *primitive_holders = new PT(EggGroup) [mesh->GetPolygonsCount()]; + for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) { + const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr); + string material_semantic = FROM_FSTRING(polygons->GetMaterialSemantic()); + + // Stores which group holds the primitives. + PT(EggGroup) primitiveholder; + // If we have materials, make a group for each material. Then, apply the + // material's per-group stuff. + if (materials != nullptr && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) { + // primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + + // material_semantic); + primitiveholder = new EggGroup; + mesh_group->add_child(primitiveholder); + } else { + primitiveholder = mesh_group; + } + primitive_holders[gr] = primitiveholder; + // Apply the per-group data of the materials, if we have it. + if (materials != nullptr) { + materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency); + } + // Find the position sources + const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION); + assert(pinput != nullptr); + const uint32* indices = pinput->GetIndices(); + // Find the normal sources + const FCDGeometrySource* nsource = mesh->FindSourceByType(FUDaeGeometryInput::NORMAL); + const FCDGeometryPolygonsInput* ninput = polygons->FindInput(FUDaeGeometryInput::NORMAL); + const uint32* nindices; + if (ninput != nullptr) nindices = ninput->GetIndices(); + // Find texcoord sources + const FCDGeometrySource* tcsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXCOORD); + const FCDGeometryPolygonsInput* tcinput = polygons->FindInput(FUDaeGeometryInput::TEXCOORD); + const uint32* tcindices; + if (tcinput != nullptr) tcindices = tcinput->GetIndices(); + // Find vcolor sources + const FCDGeometrySource* csource = mesh->FindSourceByType(FUDaeGeometryInput::COLOR); + const FCDGeometryPolygonsInput* cinput = polygons->FindInput(FUDaeGeometryInput::COLOR); + const uint32* cindices; + if (cinput != nullptr) cindices = cinput->GetIndices(); + // Find binormal sources + const FCDGeometrySource* bsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXBINORMAL); + const FCDGeometryPolygonsInput* binput = polygons->FindInput(FUDaeGeometryInput::TEXBINORMAL); + const uint32* bindices; + if (binput != nullptr) bindices = binput->GetIndices(); + // Find tangent sources + const FCDGeometrySource* tsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXTANGENT); + const FCDGeometryPolygonsInput* tinput = polygons->FindInput(FUDaeGeometryInput::TEXTANGENT); + const uint32* tindices; + if (tinput != nullptr) tindices = tinput->GetIndices(); + // Get a name for potential coordinate sets + string tcsetname; + if (materials != nullptr && tcinput != nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() + << "Assigning texcoord set " << tcinput->GetSet() + << " to semantic '" << material_semantic << "'\n"; + } + tcsetname = materials->get_uvset_name(material_semantic, + FUDaeGeometryInput::TEXCOORD, tcinput->GetSet()); + } + string tbsetname; + if (materials != nullptr && binput != nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() + << "Assigning texbinormal set " << binput->GetSet() + << " to semantic '" << material_semantic << "'\n"; + } + tbsetname = materials->get_uvset_name(material_semantic, + FUDaeGeometryInput::TEXBINORMAL, binput->GetSet()); + } + string ttsetname; + if (materials != nullptr && tinput != nullptr) { + if (daeegg_cat.is_debug()) { + daeegg_cat.debug() + << "Assigning textangent set " << tinput->GetSet() + << " to semantic '" << material_semantic << "'\n"; + } + ttsetname = materials->get_uvset_name(material_semantic, + FUDaeGeometryInput::TEXTANGENT, tinput->GetSet()); + } + // Loop through the indices and add the vertices. + for (size_t ix = 0; ix < pinput->GetIndexCount(); ++ix) { + PT_EggVertex vertex = mesh_pool->make_new_vertex(); + const float* data = &vsource->GetData()[indices[ix]*3]; + vertex->set_pos(LPoint3d(data[0], data[1], data[2])); + + if (character != nullptr) { + // If this is skinned geometry, add the vertex influences. + character->influence_vertex(indices[ix], vertex); + } + + // Process the normal + if (nsource != nullptr && ninput != nullptr) { + assert(nsource->GetStride() == 3); + data = &nsource->GetData()[nindices[ix]*3]; + vertex->set_normal(LVecBase3d(data[0], data[1], data[2])); + } + // Process the texcoords + if (tcsource != nullptr && tcinput != nullptr) { + assert(tcsource->GetStride() == 2 || tcsource->GetStride() == 3); + data = &tcsource->GetData()[tcindices[ix]*tcsource->GetStride()]; + if (tcsource->GetStride() == 2) { + vertex->set_uv(tcsetname, LPoint2d(data[0], data[1])); + } else { + vertex->set_uvw(tcsetname, LPoint3d(data[0], data[1], data[2])); + } + } + // Process the color + if (csource != nullptr && cinput != nullptr) { + assert(csource->GetStride() == 3 || csource->GetStride() == 4); + if (csource->GetStride() == 3) { + data = &csource->GetData()[cindices[ix]*3]; + vertex->set_color(LColor(data[0], data[1], data[2], 1.0f)); + } else { + data = &csource->GetData()[cindices[ix]*4]; + vertex->set_color(LColor(data[0], data[1], data[2], data[3])); + } + } + // Possibly add a UV object + if ((bsource != nullptr && binput != nullptr) || (tsource != nullptr && tinput != nullptr)) { + if (bsource != nullptr && binput != nullptr) { + assert(bsource->GetStride() == 3); + data = &bsource->GetData()[bindices[ix]*3]; + PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(tbsetname); + if (uv_obj == nullptr) { + uv_obj = new EggVertexUV(tbsetname, LTexCoordd()); + } + uv_obj->set_binormal(LVecBase3d(data[0], data[1], data[2])); + } + if (tsource != nullptr && tinput != nullptr) { + assert(tsource->GetStride() == 3); + data = &tsource->GetData()[tindices[ix]*3]; + PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(ttsetname); + if (uv_obj == nullptr) { + uv_obj = new EggVertexUV(ttsetname, LTexCoordd()); + } + uv_obj->set_tangent(LVecBase3d(data[0], data[1], data[2])); + } + } + vertex->transform(parent->get_node_to_vertex()); + } + } + // Loop again for the polygons + for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) { + const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr); + // Now loop through the faces + uint32 offset = 0; + for (size_t fa = 0; fa < polygons->GetFaceVertexCountCount(); ++fa) { + PT(EggPrimitive) primitive = nullptr; + // Create a primitive that matches the fcollada type + switch (polygons->GetPrimitiveType()) { + case FCDGeometryPolygons::LINES: + primitive = new EggLine(); + break; + case FCDGeometryPolygons::POLYGONS: + primitive = new EggPolygon(); + break; + case FCDGeometryPolygons::TRIANGLE_FANS: + primitive = new EggTriangleFan(); + break; + case FCDGeometryPolygons::TRIANGLE_STRIPS: + primitive = new EggTriangleStrip(); + break; + case FCDGeometryPolygons::POINTS: + primitive = new EggPoint(); + break; + case FCDGeometryPolygons::LINE_STRIPS: + daeegg_cat.warning() << "Linestrips not yet supported!" << endl; + break; + default: + daeegg_cat.warning() << "Unsupported primitive type found!" << endl; + } + if (primitive != nullptr) { + primitive_holders[gr]->add_child(primitive); + if (materials != nullptr) { + materials->apply_to_primitive(FROM_FSTRING(polygons->GetMaterialSemantic()), primitive); + } + for (size_t ve = 0; ve < polygons->GetFaceVertexCount(fa); ++ve) { + assert(mesh_pool->has_vertex(ve + polygons->GetFaceVertexOffset() + offset)); + primitive->add_vertex(mesh_pool->get_vertex(ve + polygons->GetFaceVertexOffset() + offset)); + } + } + offset += polygons->GetFaceVertexCount(fa); + } + } + delete[] primitive_holders; +} + +void DAEToEggConverter:: +process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) { + assert(geometry_spline != nullptr); + PT(EggGroup) result = new EggGroup(group_name); + parent->add_child(result); + // TODO: if its not a nurbs, make it convert between the types + if (geometry_spline->GetType() != FUDaeSplineType::NURBS) { + daeegg_cat.warning() << "Only NURBS curves are supported (yet)!" << endl; + } else { + // Loop through the splines + for (size_t sp = 0; sp < geometry_spline->GetSplineCount(); ++sp) { + process_spline(result, geometry_spline->GetSpline(sp)); + } + } +} + +void DAEToEggConverter:: +process_spline(EggGroup *parent, const FCDSpline* spline) { + assert(spline != nullptr); + nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS); + // Now load in the nurbs curve to the egg library + PT(EggNurbsCurve) nurbs_curve = new EggNurbsCurve(FROM_FSTRING(spline->GetName())); + parent->add_child(nurbs_curve); + // TODO: what value is this? + nurbs_curve->setup(0, ((const FCDNURBSSpline*) spline)->GetKnotCount()); + for (size_t kn = 0; kn < ((const FCDNURBSSpline*) spline)->GetKnotCount(); ++kn) { + const float* knot = ((const FCDNURBSSpline*) spline)->GetKnot(kn); + assert(knot != nullptr); + nurbs_curve->set_knot(kn, *knot); + } + for (size_t cv = 0; cv < spline->GetCVCount(); ++cv) { + PT_EggVertex c_vtx = new EggVertex(); + c_vtx->set_pos(TO_VEC3(*spline->GetCV(cv))); + c_vtx->transform(parent->get_node_to_vertex()); + nurbs_curve->add_vertex(c_vtx); + } +} + +void DAEToEggConverter:: +process_controller(EggGroup *parent, const FCDControllerInstance *instance) { + assert(instance != nullptr); + const FCDController* controller = (const FCDController *)instance->GetEntity(); + assert(controller != nullptr); + + if (get_animation_convert() == AC_none) { + // If we're exporting a static mesh, export the base geometry as-is. + const FCDGeometryMesh *mesh = controller->GetBaseGeometry()->GetMesh(); + if (mesh != nullptr) { + PT(DaeMaterials) materials = new DaeMaterials(instance); + if (daeegg_cat.is_spam()) { + daeegg_cat.spam() << "Processing mesh for controller\n"; + } + process_mesh(parent, mesh, materials); + } + } else { + // Add a character for this to the table, the mesh is processed later + PT(DaeCharacter) character = new DaeCharacter(parent, instance); + _characters.push_back(character); + } + + if (controller->IsMorph()) { + assert(controller != nullptr); + const FCDMorphController* morph_controller = controller->GetMorphController(); + assert(morph_controller != nullptr); + PT(EggTable) bundle = new EggTable(parent->get_name()); + bundle->set_table_type(EggTable::TT_bundle); + PT(EggTable) morph = new EggTable("morph"); + morph->set_table_type(EggTable::TT_table); + bundle->add_child(morph); + // Loop through the morph targets. + for (size_t mt = 0; mt < morph_controller->GetTargetCount(); ++mt) { + const FCDMorphTarget* morph_target = morph_controller->GetTarget(mt); + assert(morph_target != nullptr); + PT(EggSAnimData) target = new EggSAnimData(FROM_FSTRING(morph_target->GetGeometry()->GetName())); + if (morph_target->IsAnimated()) { + // TODO + } else { + target->add_data(morph_target->GetWeight()); + } + morph->add_child(target); + } + } +} + +void DAEToEggConverter:: +process_extra(EggGroup *group, const FCDExtra* extra) { + if (extra == nullptr) { + return; + } + nassertv(group != nullptr); + + const FCDEType* etype = extra->GetDefaultType(); + if (etype == nullptr) { + return; + } + + const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D"); + if (enode == nullptr) { + return; + } + + FCDENodeList tags; + enode->FindChildrenNodes("param", tags); + for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) { + const FCDEAttribute* attr = (*it)->FindAttribute("sid"); + if (attr) { + group->set_tag(FROM_FSTRING(attr->GetValue()), (*it)->GetContent()); + } + } +} + +LMatrix4d DAEToEggConverter:: +convert_matrix(const FMMatrix44 &matrix) { + return LMatrix4d( + matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], + matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3], + matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3], + matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]); +} + +void DAEToEggConverter:: +apply_transform(EggGroup *to, const FCDTransform* from) { + assert(from != nullptr); + assert(to != nullptr); + // to->set_transform3d(convert_matrix(from->ToMatrix()) * + // to->get_transform3d()); + switch (from->GetType()) { + case FCDTransform::TRANSLATION: + { + const FCDTTranslation *trans = (const FCDTTranslation *)from; + to->add_translate3d(TO_VEC3(trans->GetTranslation())); + } + break; + + case FCDTransform::ROTATION: + { + const FCDTRotation *rot = (const FCDTRotation *)from; + to->add_rotate3d(rot->GetAngle(), TO_VEC3(rot->GetAxis())); + } + break; + + case FCDTransform::SCALE: + { + const FCDTScale *scale = (const FCDTScale *)from; + to->add_scale3d(TO_VEC3(scale->GetScale())); + } + break; + + default: + // Either a matrix, or something we can't handle. + to->add_matrix4(convert_matrix(from->ToMatrix())); + break; + } +} diff --git a/pandatool/src/daeegg/daeToEggConverter.h b/pandatool/src/daeegg/daeToEggConverter.h new file mode 100644 index 00000000..bb3b6f55 --- /dev/null +++ b/pandatool/src/daeegg/daeToEggConverter.h @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeToEggConverter.h + * @author rdb + * @date 2008-05-08 + */ + +#ifndef DAETOEGGCONVERTER_H +#define DAETOEGGCONVERTER_H + +#include "pandatoolbase.h" +#include "somethingToEggConverter.h" +#include "eggGroup.h" +#include "eggMaterial.h" +#include "eggTexture.h" +#include "eggTable.h" +#include "eggNurbsCurve.h" + +#include "pre_fcollada_include.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daeMaterials.h" +#include "daeCharacter.h" +#include "pvector.h" // Include last + +/** + * This class supervises the construction of an EggData structure from a DAE + * file. + */ +class DAEToEggConverter : public SomethingToEggConverter { +public: + DAEToEggConverter(); + DAEToEggConverter(const DAEToEggConverter ©); + ~DAEToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + + virtual bool convert_file(const Filename &filename); + virtual DistanceUnit get_input_units(); + + bool _invert_transparency; + +private: + std::string _unit_name; + double _unit_meters; + PT(EggTable) _table; + FCDocument* _document; + FUErrorSimpleHandler* _error_handler; + DaeCharacter::JointMap _joints; + + typedef pvector Characters; + Characters _characters; + + void process_asset(); + void process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced = false); + void process_instance(EggGroup *parent, const FCDEntityInstance* instance); + void process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh, + DaeMaterials *materials, DaeCharacter *character = nullptr); + void process_spline(EggGroup *parent, const std::string group_name, FCDGeometrySpline* geometry_spline); + void process_spline(EggGroup *parent, const FCDSpline* spline); + void process_controller(EggGroup *parent, const FCDControllerInstance* instance); + void process_extra(EggGroup *group, const FCDExtra* extra); + + static LMatrix4d convert_matrix(const FMMatrix44& matrix); + void apply_transform(EggGroup *to, const FCDTransform* from); + + friend class DaeCharacter; +}; + +#endif diff --git a/pandatool/src/daeegg/fcollada_utils.h b/pandatool/src/daeegg/fcollada_utils.h new file mode 100644 index 00000000..3a8ae692 --- /dev/null +++ b/pandatool/src/daeegg/fcollada_utils.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fcollada_utils.h + * @author rdb + * @date 2008-12-22 + */ + +// This file defines some conversion tools for conversion between FCollada and +// Panda3D + +#ifndef FCOLLADA_UTILS_H +#define FCOLLADA_UTILS_H + +#include "pre_fcollada_include.h" +#include + +// Useful conversion stuff +inline LVecBase3d TO_VEC3(FMVector3 v) { + return LVecBase3d(v.x, v.y, v.z); +} +inline LVecBase4d TO_VEC4(FMVector4 v) { + return LVecBase4d(v.x, v.y, v.z, v.w); +} +inline LColor TO_COLOR(FMVector4 v) { + return LColor(v.x, v.y, v.z, v.w); +} +#define FROM_VEC3(v) (FMVector3(v[0], v[1], v[2])) +#define FROM_VEC4(v) (FMVector4(v[0], v[1], v[2], v[3])) +#define FROM_MAT4(v) (FMMatrix44(v.getData())) +#define FROM_FSTRING(fs) (std::string(fs.c_str())) + +#endif diff --git a/pandatool/src/daeegg/p3daeegg_composite1.cxx b/pandatool/src/daeegg/p3daeegg_composite1.cxx new file mode 100644 index 00000000..be3f927d --- /dev/null +++ b/pandatool/src/daeegg/p3daeegg_composite1.cxx @@ -0,0 +1,5 @@ + +#include "config_daeegg.cxx" +#include "daeCharacter.cxx" +#include "daeMaterials.cxx" +#include "daeToEggConverter.cxx" diff --git a/pandatool/src/daeegg/pre_fcollada_include.h b/pandatool/src/daeegg/pre_fcollada_include.h new file mode 100644 index 00000000..59ad054c --- /dev/null +++ b/pandatool/src/daeegg/pre_fcollada_include.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pre_fcollada_include.h + * @author rdb + * @date 2008-10-04 + */ + +// This file defines some stuff that need to be defined before one includes +// FCollada.h + +#ifndef PRE_FCOLLADA_INCLUDE_H +#define PRE_FCOLLADA_INCLUDE_H + +#ifdef FCOLLADA_VERSION + #error You must include pre_fcollada_include.h before including FCollada.h! +#endif + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +// FCollada expects LINUX to be defined on linux +#ifdef IS_LINUX + #ifndef LINUX + #define LINUX + #endif +#endif + +#define NO_LIBXML +#define FCOLLADA_NOMINMAX + +// FCollada does use global min/max. +using std::min; +using std::max; + +#endif diff --git a/pandatool/src/daeprogs/CMakeLists.txt b/pandatool/src/daeprogs/CMakeLists.txt new file mode 100644 index 00000000..42cd2f48 --- /dev/null +++ b/pandatool/src/daeprogs/CMakeLists.txt @@ -0,0 +1,11 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(HAVE_EGG AND HAVE_FCOLLADA) + + add_executable(dae2egg daeToEgg.cxx daeToEgg.h) + target_link_libraries(dae2egg p3daeegg p3eggbase p3progbase) + install(TARGETS dae2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/daeprogs/daeToEgg.cxx b/pandatool/src/daeprogs/daeToEgg.cxx new file mode 100644 index 00000000..e0074734 --- /dev/null +++ b/pandatool/src/daeprogs/daeToEgg.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeToEgg.cxx + * @author rdb + * @date 2008-05-08 + */ + +#include "daeToEgg.h" + +#include "daeToEggConverter.h" + +/** + * + */ +DAEToEgg:: +DAEToEgg(): + SomethingToEgg("COLLADA", ".dae") +{ + add_animation_options(); + add_units_options(); + add_normals_options(); + add_transform_options(); + + add_option + ("invtrans", "", false, + "Import the .dae file using inverted transparency. " + "This is useful when importing COLLADA files from some authoring tools " + "that export models with inverted transparency, such as Google SketchUp.", + &SomethingToEgg::dispatch_none, &_invert_transparency); + + set_program_brief("convert COLLADA assets into .egg files"); + set_program_description + ("This program converts .dae files (COLLADA Digital Asset Exchange) to .egg."); + + _coordinate_system = CS_yup_right; + _animation_convert = AC_both; +} + +/** + * + */ +void DAEToEgg:: +run() { + if (_animation_convert != AC_both && _animation_convert != AC_none && + _animation_convert != AC_chan && _animation_convert != AC_model) { + std::cerr << "Unsupported animation convert option.\n"; + exit(1); + } + + nout << "Reading " << _input_filename << "\n"; + + _data->set_coordinate_system(_coordinate_system); + + DAEToEggConverter converter; + converter.set_egg_data(_data); + converter._allow_errors = _allow_errors; + converter._invert_transparency = _invert_transparency; + + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + DAEToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/daeprogs/daeToEgg.h b/pandatool/src/daeprogs/daeToEgg.h new file mode 100644 index 00000000..7b7eb3aa --- /dev/null +++ b/pandatool/src/daeprogs/daeToEgg.h @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file daeToEgg.h + * @author rdb + * @date 2008-05-08 + */ + +#ifndef DAETOEGG_H +#define DAETOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "daeToEggConverter.h" + +/** + * A program to read a DAE file and generate an egg file. + */ +class DAEToEgg : public SomethingToEgg { +public: + DAEToEgg(); + + void run(); + +private: + bool _invert_transparency; +}; + +#endif diff --git a/pandatool/src/daeprogs/eggToDAE.cxx b/pandatool/src/daeprogs/eggToDAE.cxx new file mode 100644 index 00000000..d3c616c3 --- /dev/null +++ b/pandatool/src/daeprogs/eggToDAE.cxx @@ -0,0 +1,173 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDAE.cxx + * @author rdb + * @date 2008-10-04 + */ + +#include "eggToDAE.h" +#include "dcast.h" +#include "pandaVersion.h" + +#include +#include +#include + +// Useful conversion stuff +#define TO_VEC3(v) (LVecBase3d(v[0], v[1], v[2])) +#define TO_VEC4(v) (LVecBase4d(v[0], v[1], v[2], v[3])) +#define TO_COLOR(v) (LColor(v[0], v[1], v[2], v[3])) +#define FROM_VEC3(v) (FMVector3(v[0], v[1], v[2])) +#define FROM_VEC4(v) (FMVector4(v[0], v[1], v[2], v[3])) +#define FROM_MAT4(v) (FMMatrix44(v.get_data())) +#define FROM_FSTRING(fs) (fs.c_str()) + +using std::cerr; + +/** + * + */ +EggToDAE:: +EggToDAE() : + EggToSomething("COLLADA", ".dae", true, false) +{ + set_binary_output(false); + set_program_brief("convert .egg files into COLLADA asset files"); + set_program_description + ("This program converts files from the egg format to the COLLADA " + ".dae (Digital Asset Exchange) format."); + + _document = nullptr; +} + +/** + * + */ +void EggToDAE:: +run() { + nassertv(has_output_filename()); + nassertv(_data != nullptr); + + FCollada::Initialize(); + _document = FCollada::NewTopDocument(); + + // Add the contributor part to the asset + FCDAssetContributor* contributor = _document->GetAsset()->AddContributor(); + const char* user_name = getenv("USER"); + if (user_name == nullptr) user_name = getenv("USERNAME"); + if (user_name != nullptr) contributor->SetAuthor(TO_FSTRING(user_name)); + // contributor->SetSourceData(); + char authoring_tool[1024]; + snprintf(authoring_tool, 1024, "Panda3D %s eggToDAE converter | FCollada v%d.%02d", PANDA_VERSION_STR, FCOLLADA_VERSION >> 16, FCOLLADA_VERSION & 0xFFFF); + authoring_tool[1023] = 0; + contributor->SetAuthoringTool(TO_FSTRING(authoring_tool)); + + // Set coordinate system + switch (_data->get_coordinate_system()) { + case CS_zup_right: + _document->GetAsset()->SetUpAxis(FMVector3::ZAxis); + break; + case CS_yup_right: + _document->GetAsset()->SetUpAxis(FMVector3::YAxis); + break; + } + + // Now actually start processing the data. + FCDSceneNode* visual_scene = _document->AddVisualScene(); + for (EggGroupNode::iterator it = _data->begin(); it != _data->end(); ++it) { + if ((*it)->is_of_type(EggGroup::get_class_type())) { + process_node(visual_scene, DCAST(EggGroup, *it)); + } + } + + // We're done here. + FCollada::SaveDocument(_document, get_output_filename().to_os_specific().c_str()); + SAFE_DELETE(_document); + FCollada::Release(); + + // if (!out) { nout << "An error occurred while writing.\n"; exit(1); } +} + +void EggToDAE::process_node(FCDSceneNode* parent, const PT(EggGroup) node) { + assert(node != nullptr); + FCDSceneNode* scene_node = parent->AddChildNode(); + // Set the parameters + scene_node->SetDaeId(node->get_name().c_str()); + scene_node->SetJointFlag(node->is_joint()); + // Apply the transforms + apply_transform(scene_node, node); + // Recursively process sub-nodes + for (EggGroupNode::iterator it = node->begin(); it != node->end(); ++it) { + if ((*it)->is_of_type(EggGroup::get_class_type())) { + process_node(scene_node, DCAST(EggGroup, *it)); + } + } +} + +void EggToDAE::apply_transform(FCDSceneNode* to, const PT(EggGroup) from) { + assert(to != nullptr); + assert(from != nullptr); + for (int co = 0; co < from->get_num_components(); ++co) { + switch (from->get_component_type(co)) { + case EggTransform::CT_translate2d: + cerr << "Warning: ignoring non-supported 2d translation\n"; + break; + case EggTransform::CT_rotate2d: + cerr << "Warning: ignoring non-supported 2d rotation\n"; + break; + case EggTransform::CT_scale2d: + cerr << "Warning: ignoring non-supported 2d scaling\n"; + break; + case EggTransform::CT_matrix3: + cerr << "Warning: ignoring non-supported 2d matrix\n"; + break; + case EggTransform::CT_translate3d: { + FCDTTranslation* new_transform = (FCDTTranslation*) to->AddTransform(FCDTransform::TRANSLATION); + new_transform->SetTranslation(FROM_VEC3(from->get_component_vec3(co))); + break; } + case EggTransform::CT_rotate3d: { + FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION); + new_transform->SetRotation(FROM_VEC3(from->get_component_vec3(co)), from->get_component_number(co)); + break; } + case EggTransform::CT_scale3d: { + FCDTScale* new_transform = (FCDTScale*) to->AddTransform(FCDTransform::SCALE); + new_transform->SetScale(FROM_VEC3(from->get_component_vec3(co))); + break; } + case EggTransform::CT_matrix4: { + FCDTMatrix* new_transform = (FCDTMatrix*) to->AddTransform(FCDTransform::MATRIX); + new_transform->SetTransform(FROM_MAT4(from->get_component_mat4(co))); + break; } + case EggTransform::CT_rotx: { + FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION); + new_transform->SetRotation(FMVector3::XAxis, from->get_component_number(co)); + break; } + case EggTransform::CT_roty: { + FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION); + new_transform->SetRotation(FMVector3::YAxis, from->get_component_number(co)); + break; } + case EggTransform::CT_rotz: { + FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION); + new_transform->SetRotation(FMVector3::ZAxis, from->get_component_number(co)); + break; } + case EggTransform::CT_uniform_scale: { + FCDTScale* new_transform = (FCDTScale*) to->AddTransform(FCDTransform::SCALE); + new_transform->SetScale(from->get_component_number(co), from->get_component_number(co), from->get_component_number(co)); + break; } + default: + cerr << "Warning: ignoring invalid transform\n"; + } + } +} + +int main(int argc, char *argv[]) { + EggToDAE prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/daeprogs/eggToDAE.h b/pandatool/src/daeprogs/eggToDAE.h new file mode 100644 index 00000000..c7782a42 --- /dev/null +++ b/pandatool/src/daeprogs/eggToDAE.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDAE.h + * @author rdb + * @date 2008-10-04 + */ + +#ifndef EGGTODAE_H +#define EGGTODAE_H + +#include "pandatoolbase.h" +#include "eggToSomething.h" +#include "eggGroup.h" +#include "eggTransform.h" + +#include "pre_fcollada_include.h" +#include +#include + +/** + * A program to read an egg file and write a DAE file. + */ +class EggToDAE : public EggToSomething { +public: + EggToDAE(); + + void run(); + +private: + FCDocument* _document; + + void process_node(FCDSceneNode* parent, const PT(EggGroup) node); + void apply_transform(FCDSceneNode* to, const PT(EggGroup) from); + +}; + +#endif diff --git a/pandatool/src/deploy-stub/CMakeLists.txt b/pandatool/src/deploy-stub/CMakeLists.txt new file mode 100644 index 00000000..066d2925 --- /dev/null +++ b/pandatool/src/deploy-stub/CMakeLists.txt @@ -0,0 +1,42 @@ +if(NOT HAVE_PYTHON) + return() +endif() + +add_executable(deploy-stub deploy-stub.c) + +if(IS_OSX) + target_link_options(deploy-stub PRIVATE -sectcreate __PANDA __panda /dev/null) + set_target_properties(deploy-stub PROPERTIES + INSTALL_RPATH "@executable_path" + BUILD_WITH_INSTALL_RPATH ON) + +elseif(WIN32) + target_sources(deploy-stub PRIVATE frozen_dllmain.c) + +elseif(IS_LINUX OR IS_FREEBSD) + set_target_properties(deploy-stub PROPERTIES + INSTALL_RPATH "$ORIGIN" + BUILD_WITH_INSTALL_RPATH ON) + target_link_options(deploy-stub PRIVATE -Wl,--disable-new-dtags -Wl,-z,origin -rdynamic) + target_link_libraries(deploy-stub m) +endif() + +target_link_libraries(deploy-stub Python::Python) +install(TARGETS deploy-stub) + +if(WIN32 OR IS_OSX) + add_executable(deploy-stubw WIN32 deploy-stub.c) + + if(IS_OSX) + target_link_options(deploy-stubw PRIVATE -sectcreate __PANDA __panda /dev/null) + set_target_properties(deploy-stubw PROPERTIES + INSTALL_RPATH "@executable_path/../Frameworks" + BUILD_WITH_INSTALL_RPATH ON) + target_compile_definitions(deploy-stubw PRIVATE MACOS_APP_BUNDLE=1) + elseif(WIN32) + target_sources(deploy-stubw PRIVATE frozen_dllmain.c) + endif() + + target_link_libraries(deploy-stubw Python::Python) + install(TARGETS deploy-stubw) +endif() diff --git a/pandatool/src/deploy-stub/NativeInvocationHandler.java b/pandatool/src/deploy-stub/NativeInvocationHandler.java new file mode 100644 index 00000000..2a108373 --- /dev/null +++ b/pandatool/src/deploy-stub/NativeInvocationHandler.java @@ -0,0 +1,25 @@ +package org.jnius; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * Special support for pyjnius. + */ +public class NativeInvocationHandler implements InvocationHandler { + private long _ptr; + + public NativeInvocationHandler(long ptr) { + _ptr = ptr; + } + + public long getPythonObjectPointer() { + return _ptr; + } + + public Object invoke(Object proxy, Method method, Object[] args) { + return invoke0(proxy, method, args); + } + + native Object invoke0(Object proxy, Method method, Object[] args); +} diff --git a/pandatool/src/deploy-stub/android_log.c b/pandatool/src/deploy-stub/android_log.c new file mode 100644 index 00000000..83b60901 --- /dev/null +++ b/pandatool/src/deploy-stub/android_log.c @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file android_log.c + * @author rdb + * @date 2021-12-10 + */ + +#undef _POSIX_C_SOURCE +#undef _XOPEN_SOURCE +#define PY_SSIZE_T_CLEAN 1 + +#include "Python.h" +#include + +/** + * Writes a message to the Android log. + */ +static PyObject * +_py_write(PyObject *self, PyObject *args) { + int prio; + char *tag; + char *text; + if (PyArg_ParseTuple(args, "iss", &prio, &tag, &text)) { + __android_log_write(prio, tag, text); + Py_RETURN_NONE; + } + return NULL; +} + +static PyMethodDef python_simple_funcs[] = { + { "write", &_py_write, METH_VARARGS }, + { NULL, NULL } +}; + +static struct PyModuleDef android_log_module = { + PyModuleDef_HEAD_INIT, + "android_log", + NULL, + -1, + python_simple_funcs, + NULL, NULL, NULL, NULL +}; + +__attribute__((visibility("default"))) +PyObject *PyInit_android_log() { + return PyModule_Create(&android_log_module); +} diff --git a/pandatool/src/deploy-stub/android_main.cxx b/pandatool/src/deploy-stub/android_main.cxx new file mode 100644 index 00000000..d1e140c5 --- /dev/null +++ b/pandatool/src/deploy-stub/android_main.cxx @@ -0,0 +1,337 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file android_main.cxx + * @author rdb + * @date 2021-12-06 + */ + +#include "config_android.h" +#include "config_putil.h" +#include "virtualFileMountAndroidAsset.h" +#include "virtualFileSystem.h" +#include "filename.h" +#include "thread.h" +#include "urlSpec.h" + +#include "android_native_app_glue.h" + +#include "Python.h" +#include "structmember.h" + +#include +#include + +#include + +// Leave room for future expansion. +#define MAX_NUM_POINTERS 24 + +// Define an exposed symbol where we store the offset to the module data. +extern "C" { + __attribute__((__visibility__("default"), used)) + volatile struct { + uint64_t blob_offset; + uint64_t blob_size; + uint16_t version; + uint16_t num_pointers; + uint16_t codepage; + uint16_t flags; + uint64_t reserved; + void *pointers[MAX_NUM_POINTERS]; + + // The reason we initialize it to -1 is because otherwise, smart linkers may + // end up putting it in the .bss section for zero-initialized data. + } blobinfo = {(uint64_t)-1}; +} + +// Defined in android_log.c +extern "C" PyObject *PyInit_android_log(); + +/** + * Maps the binary blob at the given memory address to memory, and returns the + * pointer to the beginning of it. + */ +static void *map_blob(const char *path, off_t offset, size_t size) { + FILE *runtime = fopen(path, "rb"); + assert(runtime != NULL); + + void *blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset); + assert(blob != MAP_FAILED); + + fclose(runtime); + return blob; +} + +/** + * The inverse of map_blob. + */ +static void unmap_blob(void *blob) { + if (blob) { + munmap(blob, blobinfo.blob_size); + } +} + +/** + * This function is called by native_app_glue to initialize the program. + * + * Note that this does not run in the main thread, but in a thread created + * specifically for this activity by android_native_app_glue. + * + * Unlike the regular deploy-stub, we need to interface directly with the + * Panda3D libraries here, since we can't pass the pointers from Java to Panda + * through the Python interpreter easily. + */ +void android_main(struct android_app *app) { + panda_android_app = app; + + // Attach the app thread to the Java VM. + JNIEnv *env; + ANativeActivity *activity = app->activity; + int attach_status = activity->vm->AttachCurrentThread(&env, nullptr); + if (attach_status < 0 || env == nullptr) { + android_cat.error() << "Failed to attach thread to JVM!\n"; + return; + } + + jclass activity_class = env->GetObjectClass(activity->clazz); + + // Get the current Java thread name. This just helps with debugging. + jmethodID methodID = env->GetStaticMethodID(activity_class, "getCurrentThreadName", "()Ljava/lang/String;"); + jstring jthread_name = (jstring) env->CallStaticObjectMethod(activity_class, methodID); + + std::string thread_name; + if (jthread_name != nullptr) { + const char *c_str = env->GetStringUTFChars(jthread_name, nullptr); + thread_name.assign(c_str); + env->ReleaseStringUTFChars(jthread_name, c_str); + } + + // Before we make any Panda calls, we must make the thread known to Panda. + // This will also cause the JNIEnv pointer to be stored on the thread. + // Note that we must keep a reference to this thread around. + PT(Thread) current_thread = Thread::bind_thread(thread_name, "android_app"); + + android_cat.info() + << "New native activity started on " << *current_thread << "\n"; + + // Fetch the data directory. + jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + + jobject appinfo = env->CallObjectMethod(activity->clazz, get_appinfo); + jclass appinfo_class = env->GetObjectClass(appinfo); + + // Fetch the path to the data directory. + jfieldID datadir_field = env->GetFieldID(appinfo_class, "dataDir", "Ljava/lang/String;"); + jstring datadir = (jstring) env->GetObjectField(appinfo, datadir_field); + const char *data_path = env->GetStringUTFChars(datadir, nullptr); + + if (data_path != nullptr) { + Filename::_internal_data_dir = data_path; + android_cat.info() << "Path to data: " << data_path << "\n"; + + env->ReleaseStringUTFChars(datadir, data_path); + } + + // Get the cache directory. Set the model-path to this location. + methodID = env->GetMethodID(activity_class, "getCacheDirString", "()Ljava/lang/String;"); + jstring jcache_dir = (jstring) env->CallObjectMethod(activity->clazz, methodID); + + if (jcache_dir != nullptr) { + const char *cache_dir; + cache_dir = env->GetStringUTFChars(jcache_dir, nullptr); + android_cat.info() << "Path to cache: " << cache_dir << "\n"; + + ConfigVariableFilename model_cache_dir("model-cache-dir", Filename()); + model_cache_dir.set_value(cache_dir); + env->ReleaseStringUTFChars(jcache_dir, cache_dir); + } + + // Fetch the path to the library directory. + jfieldID libdir_field = env->GetFieldID(appinfo_class, "nativeLibraryDir", "Ljava/lang/String;"); + jstring libdir_jstr = (jstring) env->GetObjectField(appinfo, libdir_field); + const char *libdir = env->GetStringUTFChars(libdir_jstr, nullptr); + + if (libdir != nullptr) { + std::string dtool_name = std::string(libdir) + "/libp3dtool.so"; + ExecutionEnvironment::set_dtool_name(dtool_name); + android_cat.info() << "Path to dtool: " << dtool_name << "\n"; + } + + // Get the path to the APK. + methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;"); + jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID); + + const char *apk_path; + apk_path = env->GetStringUTFChars(code_path, nullptr); + android_cat.info() << "Path to APK: " << apk_path << "\n"; + + // Get the path to the native library. + methodID = env->GetMethodID(activity_class, "getNativeLibraryPath", "()Ljava/lang/String;"); + jstring lib_path_jstr = (jstring) env->CallObjectMethod(activity->clazz, methodID); + + const char *lib_path; + lib_path = env->GetStringUTFChars(lib_path_jstr, nullptr); + android_cat.info() << "Path to native library: " << lib_path << "\n"; + ExecutionEnvironment::set_binary_name(lib_path); + + // Map the blob to memory + void *blob = map_blob(lib_path, (off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); + env->ReleaseStringUTFChars(lib_path_jstr, lib_path); + assert(blob != NULL); + + assert(blobinfo.num_pointers <= MAX_NUM_POINTERS); + for (uint32_t i = 0; i < blobinfo.num_pointers; ++i) { + // Only offset if the pointer is non-NULL. Except for the first + // pointer, which may never be NULL and usually (but not always) + // points to the beginning of the blob. + if (i == 0 || blobinfo.pointers[i] != nullptr) { + blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); + } + } + + // Now load the configuration files. + ConfigPage *page = nullptr; + ConfigPageManager *cp_mgr; + const char *prc_data = (char *)blobinfo.pointers[1]; + if (prc_data != nullptr) { + cp_mgr = ConfigPageManager::get_global_ptr(); + std::istringstream in(prc_data); + page = cp_mgr->make_explicit_page("builtin"); + page->read_prc(in); + } + + // Mount the assets directory. + Filename apk_fn(apk_path); + PT(VirtualFileMountAndroidAsset) asset_mount; + asset_mount = new VirtualFileMountAndroidAsset(app->activity->assetManager, apk_fn); + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + //Filename asset_dir(apk_fn.get_dirname(), "assets"); + Filename asset_dir("/android_asset"); + vfs->mount(asset_mount, asset_dir, 0); + + // Release the apk_path. + env->ReleaseStringUTFChars(code_path, apk_path); + + // Now add the asset directory to the model-path. + //TODO: prevent it from adding the directory multiple times. + get_model_path().append_directory(asset_dir); + + // Offset the pointers in the module table using the base mmap address. + struct _frozen *moddef = (struct _frozen *)blobinfo.pointers[0]; + while (moddef->name) { + moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); + if (moddef->code != nullptr) { + moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); + } + //__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); + moddef++; + } + + PyImport_FrozenModules = (struct _frozen *)blobinfo.pointers[0]; + + PyPreConfig preconfig; + PyPreConfig_InitIsolatedConfig(&preconfig); + preconfig.utf8_mode = 1; + PyStatus status = Py_PreInitialize(&preconfig); + if (PyStatus_Exception(status)) { + env->ReleaseStringUTFChars(libdir_jstr, libdir); + Py_ExitStatusException(status); + return; + } + + // Register the android_log module. + if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) { + android_cat.error() + << "Failed to register android_log module.\n"; + env->ReleaseStringUTFChars(libdir_jstr, libdir); + return; + } + + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */ + config.buffered_stdio = 0; + config.configure_c_stdio = 0; + config.write_bytecode = 0; + PyConfig_SetBytesString(&config, &config.platlibdir, libdir); + env->ReleaseStringUTFChars(libdir_jstr, libdir); + + status = Py_InitializeFromConfig(&config); + PyConfig_Clear(&config); + if (PyStatus_Exception(status)) { + Py_ExitStatusException(status); + return; + } + + while (!app->destroyRequested) { + // Call the main module. This will not return until the app is done. + android_cat.info() << "Importing __main__\n"; + + int n = PyImport_ImportFrozenModule("__main__"); + if (n == 0) { + Py_FatalError("__main__ not frozen"); + break; + } + if (n < 0) { + if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { + PyErr_Print(); + } else { + PyErr_Clear(); + } + } + + if (app->destroyRequested) { + // The app closed responding to a destroy request. + break; + } + + // Ask Android to clean up the activity. + android_cat.info() << "Exited from __main__, finishing activity\n"; + ANativeActivity_finish(activity); + + // We still need to keep an event loop going until Android gives us leave + // to end the process. + while (!app->destroyRequested) { + int looper_id; + struct android_poll_source *source; + auto result = ALooper_pollOnce(-1, &looper_id, nullptr, (void **)&source); + if (looper_id == LOOPER_ID_MAIN) { + int8_t cmd = android_app_read_cmd(app); + android_app_pre_exec_cmd(app, cmd); + android_app_post_exec_cmd(app, cmd); + + // I don't think we can get a resume command after we call finish(), + // but let's handle it just in case. + if (cmd == APP_CMD_RESUME || cmd == APP_CMD_DESTROY) { + break; + } + } else if (source != nullptr) { + source->process(app, source); + } + } + } + + Py_Finalize(); + + android_cat.info() << "Destroy requested, exiting from android_main\n"; + + vfs->unmount(asset_mount); + + if (page != nullptr) { + cp_mgr->delete_explicit_page(page); + } + + unmap_blob(blob); + + // Detach the thread before exiting. + activity->vm->DetachCurrentThread(); + + _exit(0); +} diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c new file mode 100644 index 00000000..6cd2c280 --- /dev/null +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -0,0 +1,767 @@ +/* Python interpreter main program for frozen scripts */ + +#include "Python.h" +#ifdef _WIN32 +# include "malloc.h" +# include +#else +# include +# include +#endif + +#ifdef __FreeBSD__ +# include +#endif + +#ifdef __APPLE__ +# include +# include +#endif + +#include +#include +#include + +#include + +#include "structmember.h" + +/* Leave room for future expansion. We only read pointer 0, but there are + other pointers that are being read by configPageManager.cxx. */ +#define MAX_NUM_POINTERS 24 + +/* Stored in the flags field of the blobinfo structure below. */ +enum Flags { + F_log_append = 1, + F_log_filename_strftime = 2, + F_keep_docstrings = 4, +}; + +/* Define an exposed symbol where we store the offset to the module data. */ +#ifdef _MSC_VER +__declspec(dllexport) +#else +__attribute__((__visibility__("default"), used)) +#endif +volatile struct { + uint64_t blob_offset; + uint64_t blob_size; + uint16_t version; + uint16_t num_pointers; + uint16_t codepage; + uint16_t flags; + uint64_t reserved; + void *pointers[MAX_NUM_POINTERS]; + + // The reason we initialize it to -1 is because otherwise, smart linkers may + // end up putting it in the .bss section for zero-initialized data. +} blobinfo = {(uint64_t)-1}; + + +#ifdef _WIN32 +// These placeholders can have their names changed by deploy-stub. +__declspec(dllexport) DWORD SymbolPlaceholder___________________ = 0x00000001; +__declspec(dllexport) DWORD SymbolPlaceholder__ = 0x00000001; +#endif + +#ifdef MS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include + +extern void PyWinFreeze_ExeInit(void); +extern void PyWinFreeze_ExeTerm(void); + +static struct _inittab extensions[] = { + {0, 0}, +}; + +# define WIN_UNICODE +#endif + +#ifdef _WIN32 +static wchar_t *log_pathw = NULL; +#endif + +#if PY_VERSION_HEX >= 0x030b0000 +typedef struct { + const char *name; + const unsigned char *code; + int size; +} ModuleDef; +#else +typedef struct _frozen ModuleDef; +#endif + +/** + * Sets the main_dir field of the blobinfo structure, but only if it wasn't + * already set. + */ +static void set_main_dir(char *main_dir) { + if (blobinfo.num_pointers >= 10) { + if (blobinfo.num_pointers == 10) { + ++blobinfo.num_pointers; + blobinfo.pointers[10] = NULL; + } + if (blobinfo.pointers[10] == NULL) { + blobinfo.pointers[10] = main_dir; + } + } +} + +/** + * Creates the parent directories of the given path. Returns 1 on success. + */ +#ifdef _WIN32 +static int mkdir_parent(const wchar_t *path) { + // Copy the path to a temporary buffer. + wchar_t buffer[4096]; + size_t buflen = wcslen(path); + if (buflen + 1 >= _countof(buffer)) { + return 0; + } + wcscpy_s(buffer, _countof(buffer), path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/' || buffer[buflen] == '\\') { + buffer[buflen] = 0; + break; + } + } + if (buflen == (size_t)-1 || buflen == 0) { + // There was no path separator, or this was the root directory. + return 0; + } + + if (CreateDirectoryW(buffer, NULL) != 0) { + // Success! + return 1; + } + + // Failed. + DWORD last_error = GetLastError(); + if (last_error == ERROR_ALREADY_EXISTS) { + // Not really an error: the directory is already there. + return 1; + } + + if (last_error == ERROR_PATH_NOT_FOUND) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (CreateDirectoryW(buffer, NULL) != 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#else +static int mkdir_parent(const char *path) { + // Copy the path to a temporary buffer. + char buffer[4096]; + size_t buflen = strlen(path); + if (buflen + 1 >= sizeof(buffer)) { + return 0; + } + strcpy(buffer, path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/') { + buffer[buflen] = 0; + break; + } + } + if (buflen == (size_t)-1 || buflen == 0) { + // There was no path separator, or this was the root directory. + return 0; + } + if (mkdir(buffer, 0755) == 0) { + // Success! + return 1; + } + + // Failed. + if (errno == EEXIST) { + // Not really an error: the directory is already there. + return 1; + } + + if (errno == ENOENT || errno == EACCES) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (mkdir(buffer, 0755) == 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#endif + +/** + * Redirects the output streams to point to the log file with the given path. + * + * @param path specifies the location of log file, may start with ~ + * @param append should be nonzero if it should not truncate the log file. + */ +static int setup_logging(const char *path, int append) { +#ifdef _WIN32 + // Does it start with a tilde? Perform tilde expansion if so. + wchar_t *pathw = (wchar_t *)malloc(sizeof(wchar_t) * MAX_PATH); + pathw[0] = 0; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) { + free(pathw); + return 0; + } + offset = wcslen(pathw); + } + + // We need to convert the rest of the path from UTF-8 to UTF-16. + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset, + (int)(MAX_PATH - offset)) == 0) { + free(pathw); + return 0; + } + + DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE); + int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS; + HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + // Make the parent directories first. + mkdir_parent(pathw); + handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (handle == INVALID_HANDLE_VALUE) { + free(pathw); + return 0; + } + + log_pathw = pathw; + + if (append) { + SetFilePointer(handle, 0, NULL, FILE_END); + } + + SetStdHandle(STD_OUTPUT_HANDLE, handle); + SetStdHandle(STD_ERROR_HANDLE, handle); + + // If we are running under the UCRT in a GUI application, we can't be sure + // that we have valid fds for stdout and stderr, so we have to set them up. + // One way to do this is to reopen them to something silly (like NUL). + if (_fileno(stdout) < 0) { + _close(1); + _wfreopen(L"\\\\.\\NUL", L"w", stdout); + } + + if (_fileno(stderr) < 0) { + _close(2); + _wfreopen(L"\\\\.\\NUL", L"w", stderr); + } + + // Now replace the stdout and stderr file descriptors with one pointing to + // our desired handle. + int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | _O_APPEND); + _dup2(fd, _fileno(stdout)); + _dup2(fd, _fileno(stderr)); + _close(fd); + + return 1; +#else + // Does it start with a tilde? Perform tilde expansion if so. + char buffer[PATH_MAX * 2]; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + home_dir = getpwuid(getuid())->pw_dir; + } + offset = strlen(home_dir); + assert(offset < sizeof(buffer)); + strncpy(buffer, home_dir, sizeof(buffer)); + } + + // Copy over the rest of the path. + strcpy(buffer + offset, path); + + mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC); + int fd = open(buffer, mode, 0644); + if (fd == -1) { + // Make the parent directories first. + mkdir_parent(buffer); + fd = open(buffer, mode, 0644); + } + + if (fd == -1) { + perror(buffer); + return 0; + } + + fflush(stdout); + fflush(stderr); + + dup2(fd, 1); + dup2(fd, 2); + + if (close(fd) < 0) { + perror("setup_logging: close"); + } + return 1; +#endif +} + +/** + * Sets the line_buffering property on a TextIOWrapper object. + */ +static int enable_line_buffering(PyObject *file) { +#if PY_VERSION_HEX >= 0x03070000 + /* Python 3.7 has a useful reconfigure() method. */ + PyObject *kwargs = _PyDict_NewPresized(1); + PyDict_SetItemString(kwargs, "line_buffering", Py_True); + PyObject *args = PyTuple_New(0); + + PyObject *method = PyObject_GetAttrString(file, "reconfigure"); + if (method != NULL) { + PyObject *result = PyObject_Call(method, args, kwargs); + Py_DECREF(method); + Py_DECREF(kwargs); + Py_DECREF(args); + if (result != NULL) { + Py_DECREF(result); + } else { + PyErr_Clear(); + return 0; + } + } else { + Py_DECREF(kwargs); + Py_DECREF(args); + PyErr_Clear(); + return 0; + } +#else + /* Older versions just don't expose a way to reconfigure(), but it's still + safe to override the property; we just have to use a hack to do it, + because it's officially marked "readonly". */ + + PyTypeObject *type = Py_TYPE(file); + PyMemberDef *member = type->tp_members; + + while (member != NULL && member->name != NULL) { + if (strcmp(member->name, "line_buffering") == 0) { + *((char *)file + member->offset) = 1; + return 1; + } + ++member; + } + fflush(stdout); +#endif + return 1; +} + +/* Main program */ + +#ifdef WIN_UNICODE +int Py_FrozenMain(int argc, wchar_t **argv) +#else +int Py_FrozenMain(int argc, char **argv) +#endif +{ + char *p; + int n, sts = 1; + int unbuffered = 0; +#ifndef NDEBUG + int inspect = 0; +#endif + +#ifndef WIN_UNICODE + int i; + char *oldloc; + wchar_t **argv_copy = NULL; + /* We need a second copies, as Python might modify the first one. */ + wchar_t **argv_copy2 = NULL; + + if (argc > 0) { + argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc); + argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc); + } +#endif + + Py_FrozenFlag = 1; /* Suppress errors from getpath.c */ + Py_NoSiteFlag = 0; + Py_NoUserSiteDirectory = 1; + +#if PY_VERSION_HEX >= 0x03020000 + if (blobinfo.flags & F_keep_docstrings) { + Py_OptimizeFlag = 1; + } else { + Py_OptimizeFlag = 2; + } +#endif + +#ifndef NDEBUG + if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') + inspect = 1; +#endif + if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') + unbuffered = 1; + + if (unbuffered) { + setbuf(stdin, (char *)NULL); + setbuf(stdout, (char *)NULL); + setbuf(stderr, (char *)NULL); + } + +#ifndef WIN_UNICODE + oldloc = setlocale(LC_ALL, NULL); + setlocale(LC_ALL, ""); + for (i = 0; i < argc; i++) { + argv_copy[i] = Py_DecodeLocale(argv[i], NULL); + argv_copy2[i] = argv_copy[i]; + if (!argv_copy[i]) { + fprintf(stderr, "Unable to decode the command line argument #%i\n", + i + 1); + argc = i; + goto error; + } + } + setlocale(LC_ALL, oldloc); +#endif + +#ifdef MS_WINDOWS + PyImport_ExtendInittab(extensions); +#endif /* MS_WINDOWS */ + + if (argc >= 1) { +#ifndef WIN_UNICODE + Py_SetProgramName(argv_copy[0]); +#else + Py_SetProgramName(argv[0]); +#endif + } + + Py_Initialize(); +#ifdef MS_WINDOWS + PyWinFreeze_ExeInit(); +#endif + +#ifdef MS_WINDOWS + /* Ensure that line buffering is enabled on the output streams. */ + if (!unbuffered) { + PyObject *sys_stream; + sys_stream = PySys_GetObject("__stdout__"); + if (sys_stream && !enable_line_buffering(sys_stream)) { + fprintf(stderr, "Failed to enable line buffering on sys.stdout\n"); + fflush(stderr); + } + sys_stream = PySys_GetObject("__stderr__"); + if (sys_stream && !enable_line_buffering(sys_stream)) { + fprintf(stderr, "Failed to enable line buffering on sys.stderr\n"); + fflush(stderr); + } + } +#endif + + if (Py_VerboseFlag) + fprintf(stderr, "Python %s\n%s\n", + Py_GetVersion(), Py_GetCopyright()); + +#ifndef WIN_UNICODE + PySys_SetArgv(argc, argv_copy); +#else + PySys_SetArgv(argc, argv); +#endif + +#ifdef MACOS_APP_BUNDLE + // Add the Frameworks directory to sys.path. + char buffer[PATH_MAX]; + uint32_t bufsize = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &bufsize) != 0) { + assert(false); + return 1; + } + char resolved[PATH_MAX]; + if (!realpath(buffer, resolved)) { + perror("realpath"); + return 1; + } + const char *dir = dirname(resolved); + sprintf(buffer, "%s/../Frameworks", dir); + + PyObject *sys_path = PyList_New(1); + PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer)); + PySys_SetObject("path", sys_path); + Py_DECREF(sys_path); + + // Now, store a path to the Resources directory into the main_dir pointer, + // for ConfigPageManager to read out and assign to MAIN_DIR. + sprintf(buffer, "%s/../Resources", dir); + set_main_dir(buffer); + + // Finally, chdir to it, so that regular Python files are read from the + // right location. + chdir(buffer); +#endif + + n = PyImport_ImportFrozenModule("__main__"); + if (n == 0) + Py_FatalError("__main__ not frozen"); + if (n < 0) { + PyErr_Print(); + sts = 1; + } + else + sts = 0; + +#ifndef NDEBUG + if (inspect && isatty((int)fileno(stdin))) + sts = PyRun_AnyFile(stdin, "") != 0; +#endif + +#ifdef MS_WINDOWS + PyWinFreeze_ExeTerm(); +#endif + Py_Finalize(); + +#ifndef WIN_UNICODE +error: + if (argv_copy2) { + for (i = 0; i < argc; i++) { + PyMem_RawFree(argv_copy2[i]); + } + } +#endif + return sts; +} + +/** + * Maps the binary blob at the given memory address to memory, and returns the + * pointer to the beginning of it. + */ +static void *map_blob(off_t offset, size_t size) { + void *blob; + FILE *runtime; + +#ifdef _WIN32 + wchar_t buffer[2048]; + GetModuleFileNameW(NULL, buffer, 2048); + runtime = _wfopen(buffer, L"rb"); +#elif defined(__FreeBSD__) + size_t bufsize = 4096; + char buffer[4096]; + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + mib[3] = getpid(); + if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) { + perror("sysctl"); + return NULL; + } + runtime = fopen(buffer, "rb"); +#elif defined(__APPLE__) + char buffer[4096]; + uint32_t bufsize = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &bufsize) != 0) { + return NULL; + } + runtime = fopen(buffer, "rb"); +#else + char buffer[4096]; + ssize_t pathlen = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (pathlen <= 0) { + perror("readlink(/proc/self/exe)"); + return NULL; + } + buffer[pathlen] = '\0'; + runtime = fopen(buffer, "rb"); +#endif + + // Get offsets. In version 0, we read it from the end of the file. + if (blobinfo.version == 0) { + uint64_t end, begin; + fseek(runtime, -8, SEEK_END); + end = ftell(runtime); + fread(&begin, 8, 1, runtime); + + offset = (off_t)begin; + size = (size_t)(end - begin); + } + + // mmap the section indicated by the offset (or malloc/fread on windows) +#ifdef _WIN32 + blob = (void *)malloc(size); + assert(blob != NULL); + fseek(runtime, (long)offset, SEEK_SET); + fread(blob, size, 1, runtime); +#else + blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset); + assert(blob != MAP_FAILED); +#endif + + fclose(runtime); + return blob; +} + +/** + * The inverse of map_blob. + */ +static void unmap_blob(void *blob) { + if (blob) { +#ifdef _WIN32 + free(blob); +#else + munmap(blob, blobinfo.blob_size); +#endif + } +} + +/** + * Main entry point to deploy-stub. + */ +#ifdef _WIN32 +int wmain(int argc, wchar_t *argv[]) { +#else +int main(int argc, char *argv[]) { +#endif + int retval; + ModuleDef *moddef; + const char *log_filename; + void *blob = NULL; + log_filename = NULL; + +#ifdef __APPLE__ + // Strip a -psn_xxx argument passed in by macOS when run from an .app bundle. + if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) { + argv[1] = argv[0]; + ++argv; + --argc; + } +#endif + + /* + printf("blob_offset: %d\n", (int)blobinfo.blob_offset); + printf("blob_size: %d\n", (int)blobinfo.blob_size); + printf("version: %d\n", (int)blobinfo.version); + printf("num_pointers: %d\n", (int)blobinfo.num_pointers); + printf("codepage: %d\n", (int)blobinfo.codepage); + printf("flags: %d\n", (int)blobinfo.flags); + printf("reserved: %d\n", (int)blobinfo.reserved); + */ + + // If we have a blob offset, we have to map the blob to memory. + if (blobinfo.version == 0 || blobinfo.blob_offset != 0) { + void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); + assert(blob != NULL); + + // Offset the pointers in the header using the base mmap address. + if (blobinfo.version > 0 && blobinfo.num_pointers > 0) { + uint32_t i; + assert(blobinfo.num_pointers <= MAX_NUM_POINTERS); + for (i = 0; i < blobinfo.num_pointers; ++i) { + // Only offset if the pointer is non-NULL. Except for the first + // pointer, which may never be NULL and usually (but not always) + // points to the beginning of the blob. + if (i == 0 || blobinfo.pointers[i] != 0) { + blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); + } + } + if (blobinfo.num_pointers >= 12) { + log_filename = blobinfo.pointers[11]; + } + } else { + blobinfo.pointers[0] = blob; + } + + // Offset the pointers in the module table using the base mmap address. + moddef = blobinfo.pointers[0]; +#if PY_VERSION_HEX < 0x030b0000 + PyImport_FrozenModules = moddef; +#endif + while (moddef->name) { + moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); + if (moddef->code != 0) { + moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); + } + //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); + moddef++; + } + + // In Python 3.11, we need to convert this to the new structure format. +#if PY_VERSION_HEX >= 0x030b0000 + ModuleDef *moddef_end = moddef; + ptrdiff_t num_modules = moddef - (ModuleDef *)blobinfo.pointers[0]; + struct _frozen *new_moddef = (struct _frozen *)calloc(num_modules + 1, sizeof(struct _frozen)); + PyImport_FrozenModules = new_moddef; + for (moddef = blobinfo.pointers[0]; moddef < moddef_end; ++moddef) { + new_moddef->name = moddef->name; + new_moddef->code = moddef->code; + new_moddef->size = moddef->size < 0 ? -(moddef->size) : moddef->size; + new_moddef->is_package = moddef->size < 0; +#if PY_VERSION_HEX < 0x030d0000 // 3.13 + new_moddef->get_code = NULL; +#endif + new_moddef++; + } +#endif + } else { + PyImport_FrozenModules = blobinfo.pointers[0]; + } + + if (log_filename != NULL) { + char log_filename_buf[4096]; + if (blobinfo.flags & F_log_filename_strftime) { + log_filename_buf[0] = 0; + time_t now = time(NULL); + if (strftime(log_filename_buf, sizeof(log_filename_buf), log_filename, localtime(&now)) > 0) { + log_filename = log_filename_buf; + } + } + setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0); + } + +#ifdef _WIN32 + if (blobinfo.codepage != 0) { + SetConsoleCP(blobinfo.codepage); + SetConsoleOutputCP(blobinfo.codepage); + } +#endif + + // Run frozen application + retval = Py_FrozenMain(argc, argv); + + fflush(stdout); + fflush(stderr); + +#if PY_VERSION_HEX >= 0x030b0000 + free((void *)PyImport_FrozenModules); + PyImport_FrozenModules = NULL; +#endif + + unmap_blob(blob); + return retval; +} + +#ifdef WIN_UNICODE +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) { + return wmain(__argc, __wargv); +} +#elif defined(_WIN32) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow) { + return main(__argc, __argv); +} +#endif diff --git a/pandatool/src/deploy-stub/frozen_dllmain.c b/pandatool/src/deploy-stub/frozen_dllmain.c new file mode 100644 index 00000000..3e374cf5 --- /dev/null +++ b/pandatool/src/deploy-stub/frozen_dllmain.c @@ -0,0 +1,134 @@ +/* FreezeDLLMain.cpp + +This is a DLLMain suitable for frozen applications/DLLs on +a Windows platform. + +The general problem is that many Python extension modules may define +DLL main functions, but when statically linked together to form +a frozen application, this DLLMain symbol exists multiple times. + +The solution is: +* Each module checks for a frozen build, and if so, defines its DLLMain + function as "__declspec(dllexport) DllMain%module%" + (eg, DllMainpythoncom, or DllMainpywintypes) + +* The frozen .EXE/.DLL links against this module, which provides + the single DllMain. + +* This DllMain attempts to locate and call the DllMain for each + of the extension modules. + +* This code also has hooks to "simulate" DllMain when used from + a frozen .EXE. + +At this stage, there is a static table of "possibly embedded modules". +This should change to something better, but it will work OK for now. + +Note that this scheme does not handle dependencies in the order +of DllMain calls - except it does call pywintypes first :-) + +As an example of how an extension module with a DllMain should be +changed, here is a snippet from the pythoncom extension module. + + // end of example code from pythoncom's DllMain.cpp + #ifndef BUILD_FREEZE + #define DLLMAIN DllMain + #define DLLMAIN_DECL + #else + #define DLLMAIN DllMainpythoncom + #define DLLMAIN_DECL __declspec(dllexport) + #endif + + extern "C" DLLMAIN_DECL + BOOL WINAPI DLLMAIN(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) + // end of example code from pythoncom's DllMain.cpp + +***************************************************************************/ +#include "windows.h" + +static char *possibleModules[] = { + "pywintypes", + "pythoncom", + "win32ui", + NULL, +}; + +BOOL CallModuleDllMain(char *modName, DWORD dwReason); + + +/* + Called by a frozen .EXE only, so that built-in extension + modules are initialized correctly +*/ +void PyWinFreeze_ExeInit(void) +{ + char **modName; + for (modName = possibleModules;*modName;*modName++) { +/* printf("Initialising '%s'\n", *modName); */ + CallModuleDllMain(*modName, DLL_PROCESS_ATTACH); + } +} + +/* + Called by a frozen .EXE only, so that built-in extension + modules are cleaned up +*/ +void PyWinFreeze_ExeTerm(void) +{ + // Must go backwards + char **modName; + for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2; + modName >= possibleModules; + *modName--) { +/* printf("Terminating '%s'\n", *modName);*/ + CallModuleDllMain(*modName, DLL_PROCESS_DETACH); + } +} + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + BOOL ret = TRUE; + switch (dwReason) { + case DLL_PROCESS_ATTACH: + { + char **modName; + for (modName = possibleModules;*modName;*modName++) { + BOOL ok = CallModuleDllMain(*modName, dwReason); + if (!ok) + ret = FALSE; + } + break; + } + case DLL_PROCESS_DETACH: + { + // Must go backwards + char **modName; + for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2; + modName >= possibleModules; + *modName--) + CallModuleDllMain(*modName, DLL_PROCESS_DETACH); + break; + } + } + return ret; +} + +BOOL CallModuleDllMain(char *modName, DWORD dwReason) +{ + BOOL (WINAPI * pfndllmain)(HINSTANCE, DWORD, LPVOID); + + char funcName[255]; + HMODULE hmod = GetModuleHandleW(NULL); + strcpy(funcName, "_DllMain"); + strcat(funcName, modName); + strcat(funcName, "@12"); // stdcall convention. + pfndllmain = (BOOL (WINAPI *)(HINSTANCE, DWORD, LPVOID))GetProcAddress(hmod, funcName); + if (pfndllmain==NULL) { + /* No function by that name exported - then that module does + not appear in our frozen program - return OK + */ + return TRUE; + } + return (*pfndllmain)(hmod, dwReason, NULL); +} + diff --git a/pandatool/src/dxf/CMakeLists.txt b/pandatool/src/dxf/CMakeLists.txt new file mode 100644 index 00000000..c8095a32 --- /dev/null +++ b/pandatool/src/dxf/CMakeLists.txt @@ -0,0 +1,20 @@ +set(P3DXF_HEADERS + dxfFile.h + dxfLayer.h + dxfLayerMap.h + dxfVertex.h +) + +set(P3DXF_SOURCES + dxfFile.cxx + dxfLayer.cxx + dxfLayerMap.cxx + dxfVertex.cxx +) + +composite_sources(p3dxf P3DXF_SOURCES) +add_library(p3dxf STATIC ${P3DXF_HEADERS} ${P3DXF_SOURCES}) +target_link_libraries(p3dxf p3pandatoolbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/dxf/dxfFile.cxx b/pandatool/src/dxf/dxfFile.cxx new file mode 100644 index 00000000..f5bb6e09 --- /dev/null +++ b/pandatool/src/dxf/dxfFile.cxx @@ -0,0 +1,937 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfFile.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfFile.h" +#include "string_utils.h" +#include "virtualFileSystem.h" + +using std::istream; +using std::ostream; +using std::string; + +DXFFile::Color DXFFile::_colors[DXF_num_colors] = { + { 1, 1, 1 }, // Color 0 is not used. + { 1, 0, 0 }, // Color 1 = Red + { 1, 1, 0 }, // Color 2 = Yellow + { 0, 1, 0 }, // Color 3 = Green + { 0, 1, 1 }, // Color 4 = Cyan + { 0, 0, 1 }, // Color 5 = Blue + { 1, 0, 1 }, // Color 6 = Magenta + { 1, 1, 1 }, // Color 7 = Black/White + { 0.3, 0.3, 0.3 }, // Color 8 = Gray + { 0.7, 0.7, 0.7 }, // Color 9 = Gray + { 1, 0, 0 }, // Remaining colors are from the fancy palette. + { 1, 0.5, 0.5 }, + { 0.65, 0, 0 }, + { 0.65, 0.325, 0.325 }, + { 0.5, 0, 0 }, + { 0.5, 0.25, 0.25 }, + { 0.3, 0, 0 }, + { 0.3, 0.15, 0.15 }, + { 0.15, 0, 0 }, + { 0.15, 0.075, 0.075 }, + { 1, 0.25, 0 }, + { 1, 0.625, 0.5 }, + { 0.65, 0.1625, 0 }, + { 0.65, 0.4063, 0.325 }, + { 0.5, 0.125, 0 }, + { 0.5, 0.3125, 0.25 }, + { 0.3, 0.075, 0 }, + { 0.3, 0.1875, 0.15 }, + { 0.15, 0.0375, 0 }, + { 0.15, 0.0938, 0.075 }, + { 1, 0.5, 0 }, + { 1, 0.75, 0.5 }, + { 0.65, 0.325, 0 }, + { 0.65, 0.4875, 0.325 }, + { 0.5, 0.25, 0 }, + { 0.5, 0.375, 0.25 }, + { 0.3, 0.15, 0 }, + { 0.3, 0.225, 0.15 }, + { 0.15, 0.075, 0 }, + { 0.15, 0.1125, 0.075 }, + { 1, 0.75, 0 }, + { 1, 0.875, 0.5 }, + { 0.65, 0.4875, 0 }, + { 0.65, 0.5688, 0.325 }, + { 0.5, 0.375, 0 }, + { 0.5, 0.4375, 0.25 }, + { 0.3, 0.225, 0 }, + { 0.3, 0.2625, 0.15 }, + { 0.15, 0.1125, 0 }, + { 0.15, 0.1313, 0.075 }, + { 1, 1, 0 }, + { 1, 1, 0.5 }, + { 0.65, 0.65, 0 }, + { 0.65, 0.65, 0.325 }, + { 0.5, 0.5, 0 }, + { 0.5, 0.5, 0.25 }, + { 0.3, 0.3, 0 }, + { 0.3, 0.3, 0.15 }, + { 0.15, 0.15, 0 }, + { 0.15, 0.15, 0.075 }, + { 0.75, 1, 0 }, + { 0.875, 1, 0.5 }, + { 0.4875, 0.65, 0 }, + { 0.5688, 0.65, 0.325 }, + { 0.375, 0.5, 0 }, + { 0.4375, 0.5, 0.25 }, + { 0.225, 0.3, 0 }, + { 0.2625, 0.3, 0.15 }, + { 0.1125, 0.15, 0 }, + { 0.1313, 0.15, 0.075 }, + { 0.5, 1, 0 }, + { 0.75, 1, 0.5 }, + { 0.325, 0.65, 0 }, + { 0.4875, 0.65, 0.325 }, + { 0.25, 0.5, 0 }, + { 0.375, 0.5, 0.25 }, + { 0.15, 0.3, 0 }, + { 0.225, 0.3, 0.15 }, + { 0.075, 0.15, 0 }, + { 0.1125, 0.15, 0.075 }, + { 0.25, 1, 0 }, + { 0.625, 1, 0.5 }, + { 0.1625, 0.65, 0 }, + { 0.4063, 0.65, 0.325 }, + { 0.125, 0.5, 0 }, + { 0.3125, 0.5, 0.25 }, + { 0.075, 0.3, 0 }, + { 0.1875, 0.3, 0.15 }, + { 0.0375, 0.15, 0 }, + { 0.0938, 0.15, 0.075 }, + { 0, 1, 0 }, + { 0.5, 1, 0.5 }, + { 0, 0.65, 0 }, + { 0.325, 0.65, 0.325 }, + { 0, 0.5, 0 }, + { 0.25, 0.5, 0.25 }, + { 0, 0.3, 0 }, + { 0.15, 0.3, 0.15 }, + { 0, 0.15, 0 }, + { 0.075, 0.15, 0.075 }, + { 0, 1, 0.25 }, + { 0.5, 1, 0.625 }, + { 0, 0.65, 0.1625 }, + { 0.325, 0.65, 0.4063 }, + { 0, 0.5, 0.125 }, + { 0.25, 0.5, 0.3125 }, + { 0, 0.3, 0.075 }, + { 0.15, 0.3, 0.1875 }, + { 0, 0.15, 0.0375 }, + { 0.075, 0.15, 0.0938 }, + { 0, 1, 0.5 }, + { 0.5, 1, 0.75 }, + { 0, 0.65, 0.325 }, + { 0.325, 0.65, 0.4875 }, + { 0, 0.5, 0.25 }, + { 0.25, 0.5, 0.375 }, + { 0, 0.3, 0.15 }, + { 0.15, 0.3, 0.225 }, + { 0, 0.15, 0.075 }, + { 0.075, 0.15, 0.1125 }, + { 0, 1, 0.75 }, + { 0.5, 1, 0.875 }, + { 0, 0.65, 0.4875 }, + { 0.325, 0.65, 0.5688 }, + { 0, 0.5, 0.375 }, + { 0.25, 0.5, 0.4375 }, + { 0, 0.3, 0.225 }, + { 0.15, 0.3, 0.2625 }, + { 0, 0.15, 0.1125 }, + { 0.075, 0.15, 0.1313 }, + { 0, 1, 1 }, + { 0.5, 1, 1 }, + { 0, 0.65, 0.65 }, + { 0.325, 0.65, 0.65 }, + { 0, 0.5, 0.5 }, + { 0.25, 0.5, 0.5 }, + { 0, 0.3, 0.3 }, + { 0.15, 0.3, 0.3 }, + { 0, 0.15, 0.15 }, + { 0.075, 0.15, 0.15 }, + { 0, 0.75, 1 }, + { 0.5, 0.875, 1 }, + { 0, 0.4875, 0.65 }, + { 0.325, 0.5688, 0.65 }, + { 0, 0.375, 0.5 }, + { 0.25, 0.4375, 0.5 }, + { 0, 0.225, 0.3 }, + { 0.15, 0.2625, 0.3 }, + { 0, 0.1125, 0.15 }, + { 0.075, 0.1313, 0.15 }, + { 0, 0.5, 1 }, + { 0.5, 0.75, 1 }, + { 0, 0.325, 0.65 }, + { 0.325, 0.4875, 0.65 }, + { 0, 0.25, 0.5 }, + { 0.25, 0.375, 0.5 }, + { 0, 0.15, 0.3 }, + { 0.15, 0.225, 0.3 }, + { 0, 0.075, 0.15 }, + { 0.075, 0.1125, 0.15 }, + { 0, 0.25, 1 }, + { 0.5, 0.625, 1 }, + { 0, 0.1625, 0.65 }, + { 0.325, 0.4063, 0.65 }, + { 0, 0.125, 0.5 }, + { 0.25, 0.3125, 0.5 }, + { 0, 0.075, 0.3 }, + { 0.15, 0.1875, 0.3 }, + { 0, 0.0375, 0.15 }, + { 0.075, 0.0938, 0.15 }, + { 0, 0, 1 }, + { 0.5, 0.5, 1 }, + { 0, 0, 0.65 }, + { 0.325, 0.325, 0.65 }, + { 0, 0, 0.5 }, + { 0.25, 0.25, 0.5 }, + { 0, 0, 0.3 }, + { 0.15, 0.15, 0.3 }, + { 0, 0, 0.15 }, + { 0.075, 0.075, 0.15 }, + { 0.25, 0, 1 }, + { 0.625, 0.5, 1 }, + { 0.1625, 0, 0.65 }, + { 0.4063, 0.325, 0.65 }, + { 0.125, 0, 0.5 }, + { 0.3125, 0.25, 0.5 }, + { 0.075, 0, 0.3 }, + { 0.1875, 0.15, 0.3 }, + { 0.0375, 0, 0.15 }, + { 0.0938, 0.075, 0.15 }, + { 0.5, 0, 1 }, + { 0.75, 0.5, 1 }, + { 0.325, 0, 0.65 }, + { 0.4875, 0.325, 0.65 }, + { 0.25, 0, 0.5 }, + { 0.375, 0.25, 0.5 }, + { 0.15, 0, 0.3 }, + { 0.225, 0.15, 0.3 }, + { 0.075, 0, 0.15 }, + { 0.1125, 0.075, 0.15 }, + { 0.75, 0, 1 }, + { 0.875, 0.5, 1 }, + { 0.4875, 0, 0.65 }, + { 0.5688, 0.325, 0.65 }, + { 0.375, 0, 0.5 }, + { 0.4375, 0.25, 0.5 }, + { 0.225, 0, 0.3 }, + { 0.2625, 0.15, 0.3 }, + { 0.1125, 0, 0.15 }, + { 0.1313, 0.075, 0.15 }, + { 1, 0, 1 }, + { 1, 0.5, 1 }, + { 0.65, 0, 0.65 }, + { 0.65, 0.325, 0.65 }, + { 0.5, 0, 0.5 }, + { 0.5, 0.25, 0.5 }, + { 0.3, 0, 0.3 }, + { 0.3, 0.15, 0.3 }, + { 0.15, 0, 0.15 }, + { 0.15, 0.075, 0.15 }, + { 1, 0, 0.75 }, + { 1, 0.5, 0.875 }, + { 0.65, 0, 0.4875 }, + { 0.65, 0.325, 0.5688 }, + { 0.5, 0, 0.375 }, + { 0.5, 0.25, 0.4375 }, + { 0.3, 0, 0.225 }, + { 0.3, 0.15, 0.2625 }, + { 0.15, 0, 0.1125 }, + { 0.15, 0.075, 0.1313 }, + { 1, 0, 0.5 }, + { 1, 0.5, 0.75 }, + { 0.65, 0, 0.325 }, + { 0.65, 0.325, 0.4875 }, + { 0.5, 0, 0.25 }, + { 0.5, 0.25, 0.375 }, + { 0.3, 0, 0.15 }, + { 0.3, 0.15, 0.225 }, + { 0.15, 0, 0.075 }, + { 0.15, 0.075, 0.1125 }, + { 1, 0, 0.25 }, + { 1, 0.5, 0.625 }, + { 0.65, 0, 0.1625 }, + { 0.65, 0.325, 0.4063 }, + { 0.5, 0, 0.125 }, + { 0.5, 0.25, 0.3125 }, + { 0.3, 0, 0.075 }, + { 0.3, 0.15, 0.1875 }, + { 0.15, 0, 0.0375 }, + { 0.15, 0.075, 0.0938 }, + { 0.33, 0.33, 0.33 }, + { 0.464, 0.464, 0.464 }, + { 0.598, 0.598, 0.598 }, + { 0.732, 0.732, 0.732 }, + { 0.866, 0.866, 0.866 }, + { 1, 1, 1 }, +}; + + +/** + * + */ +DXFFile:: +DXFFile() { + _in = nullptr; + _owns_in = false; + _layer = nullptr; + reset_entity(); + _color_index = -1; +} + +/** + * + */ +DXFFile:: +~DXFFile() { + if (_owns_in) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->close_read_file(_in); + } +} + + +/** + * Opens the indicated filename and reads it as a DXF file. + */ +void DXFFile:: +process(Filename filename) { + filename.set_text(); + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + istream *in = vfs->open_read_file(filename, true); + if (in == nullptr) { + return; + } + process(in, true); +} + + +/** + * Reads the indicated stream as a DXF file. If owns_in is true, then the + * istream will be deleted via vfs->close_read_file() when the DXFFile object + * destructs. + */ +void DXFFile:: +process(istream *in, bool owns_in) { + if (_owns_in) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->close_read_file(_in); + } + _in = in; + _owns_in = owns_in; + _state = ST_top; + + begin_file(); + while (_state != ST_done && _state != ST_error) { + if (get_group()) { + switch (_state) { + case ST_top: + state_top(); + break; + + case ST_section: + state_section(); + break; + + case ST_entity: + state_entity(); + break; + + case ST_verts: + state_verts(); + break; + + default: + break; + } + } + } +} + + + +/** + * A hook for user code, if desired. This function is called whenever + * processing begins on the DXF file. + */ +void DXFFile:: +begin_file() { +} + + +/** + * A hook for user code, if desired. This function is called whenever a new + * section in the DXF file is encountered. + */ +void DXFFile:: +begin_section() { +} + + +/** + * A hook for user code, if desired. This function is called whenever a + * vertex is read from the DXF file. This function has the default behavior + * of adding the vertex to the _verts list, so that when done_entity() is + * called later, it will have the complete list of vertices available to it. + */ +void DXFFile:: +done_vertex() { + DXFVertex v; + v._p = _p; + _verts.push_back(v); +} + + +/** + * This is the primary hook for user code. This function is called when an + * entity is read from the DXF file. This may be something like a polygon, + * point, or a polygon mesh: any geometry. It is up to the user code to + * override this function and do something interesting with each piece of + * geometry that is read. + */ +void DXFFile:: +done_entity() { +} + + +/** + * A hook for user code, if desired. This function is called as each section + * in the DXF file is finished. + */ +void DXFFile:: +end_section() { +} + + +/** + * A hook for user code, if desired. This function is called when the DXF + * processing is complete. + */ +void DXFFile:: +end_file() { +} + + +/** + * A hook for user code, if desired. This function is called when some + * unexpected error occurs while reading the DXF file. + */ +void DXFFile:: +error() { + nout << "Error!\n"; +} + + + +/** + * Returns the index of the closest matching AutoCAD color to the indicated r, + * g, b. + */ +int DXFFile:: +find_color(double r, double g, double b) { + double best_diff = 4.0; // 4 is greater than our expected max, 3. + int best_index = 7; + + for (int i = 0; i < 255; i++) { + double diff = ((r - _colors[i].r) * (r - _colors[i].r) + + (g - _colors[i].g) * (g - _colors[i].g) + + (b - _colors[i].b) * (b - _colors[i].b)); + if (diff < best_diff) { + best_diff = diff; + best_index = i; + } + } + + return best_index; +} + +/** + * This is a convenience function to return the r,g,b color of the current + * entity (at the time of done_entity()). It's based on the _color_index + * value that was read from the DXF file. + */ +const DXFFile::Color &DXFFile:: +get_color() const { + if (_color_index >= 0 && _color_index <= 255) { + return _colors[_color_index]; + } + return _colors[0]; +} + + +/** + * Assuming the current entity is a planar-based entity, for instance, a 2-d + * polygon (as opposed to a 3-d polygon), this converts the coordinates from + * the funny planar coordinate system to the world coordinates. It converts + * the _p value of the entity, as well as all vertices in the _verts list. + */ +void DXFFile:: +ocs_2_wcs() { + compute_ocs(); + + // Convert the entity's position. + _p = _p * _ocs2wcs; + + // Maybe we have these coordinates too. + _q = _q * _ocs2wcs; + _r = _r * _ocs2wcs; + _s = _s * _ocs2wcs; + + // If there are any vertices, convert them too. + DXFVertices::iterator vi; + for (vi = _verts.begin(); vi != _verts.end(); ++vi) { + (*vi)._p = (*vi)._p * _ocs2wcs; + } +} + + +/** + * Computes the matrix used to convert from the planar coordinate system to + * world coordinates. + */ +void DXFFile:: +compute_ocs() { + // A 2-d entity's vertices might be defined in an "Object Coordinate System" + // which has a funny definition. Its Z axis is defined by _z, and its X and + // Y axes are inferred from that. The origin is the same as the world + // coordinate system's origin. + + // The Z axis is _z. Determine the x and y axes. + LVector3d x, y; + + if (fabs(_z[0]) < 1.0/64.0 && fabs(_z[1]) < 1.0/64.0) { + x = cross(LVector3d(0.0, 1.0, 0.0), _z); + } else { + x = cross(LVector3d(0.0, 0.0, 1.0), _z); + } + x.normalize(); + y = cross(x, _z); + y.normalize(); + + // Now build a rotate matrix from these vectors. + LMatrix4d + ocs( x[0], x[1], x[2], 0, + y[0], y[1], y[2], 0, + _z[0], _z[1], _z[2], 0, + 0, 0, 0, 1); + + _ocs2wcs.invert_from(ocs); +} + + +/** + * Reads the next code, string pair from the DXF file. This is the basic unit + * of data in a DXF file. + */ +bool DXFFile:: +get_group() { + istream &in = *_in; + do { + in >> _code; + if (!in) { + change_state(ST_error); + return false; + } + + // Now skip past exactly one newline character and any number of other + // whitespace characters. + while (in && in.peek() != '\n') { + in.get(); + } + in.get(); + while (in && isspace(in.peek()) && in.peek() != '\n') { + in.get(); + } + + std::getline(in, _string); + _string = trim_right(_string); + + if (!in) { + change_state(ST_error); + return false; + } + + // If we just read a comment, go back and get another one. + } while (_code == 999); + + return true; +} + + +/** + * Called as new nodes are read to update the internal state correctly. + */ +void DXFFile:: +change_state(State new_state) { + if (_state == ST_verts) { + done_vertex(); + _p.set(0.0, 0.0, 0.0); + _q.set(0.0, 0.0, 0.0); + _r.set(0.0, 0.0, 0.0); + _s.set(0.0, 0.0, 0.0); + } + if ((_state == ST_entity || _state == ST_verts) && + new_state != ST_verts) { + // We finish an entity when we read a new entity, or when we've read the + // last vertex (if we were scanning the vertices after an entity). + done_entity(); + reset_entity(); + } + switch (new_state) { + case ST_top: + end_section(); + break; + + case ST_done: + end_file(); + break; + + default: + break; + } + _state = new_state; +} + + +/** + * + */ +void DXFFile:: +change_section(Section new_section) { + change_state(ST_section); + _section = new_section; + begin_section(); +} + + +/** + * Given a newly read layer name, sets the _layer pointer to point to the + * associate layer. If the layer name has not been encountered before, + * creates a new layer definition. + */ +void DXFFile:: +change_layer(const string &layer_name) { + if (_layer == nullptr || _layer->get_name() != layer_name) { + _layer = _layers.get_layer(layer_name, this); + } +} + + +/** + * + */ +void DXFFile:: +change_entity(Entity new_entity) { + if (new_entity == EN_vertex && _vertices_follow) { + // If we read a new vertex and we're still scanning the vertices that + // follow an entity, keep scanning it--we haven't finished the entity yet. + change_state(ST_verts); + + } else { + // Otherwise, begin a new entity. + change_state(ST_entity); + _entity = new_entity; + } +} + + +/** + * Resets the current entity to its initial, default state prior to reading a + * new entity. + */ +void DXFFile:: +reset_entity() { + _p.set(0.0, 0.0, 0.0); + _q.set(0.0, 0.0, 0.0); + _r.set(0.0, 0.0, 0.0); + _s.set(0.0, 0.0, 0.0); + _z.set(0.0, 0.0, 1.0); + _vertices_follow = false; + // _color_index = -1; + + _verts.erase(_verts.begin(), _verts.end()); +} + + +/** + * Does the DXF processing when we are at the top of the file, outside of any + * section. + */ +void DXFFile:: +state_top() { + if (_code != 0) { + nout << "Group code 0 not found at top level; found code " << _code + << " instead.\n"; + change_state(ST_error); + } else { + if (_string == "SECTION") { + if (get_group()) { + if (_code != 2) { + nout << "Group code 0 not immediately followed by code 2; found code " + << _code << " instead.\n"; + } else { + if (_string == "HEADER") { + change_section(SE_header); + } else if (_string == "TABLES") { + change_section(SE_tables); + } else if (_string == "BLOCKS") { + change_section(SE_blocks); + } else if (_string == "ENTITIES") { + change_section(SE_entities); + } else if (_string == "OBJECTS") { + change_section(SE_objects); + } else { + change_section(SE_unknown); + } + } + } + } else if (_string == "EOF") { + change_state(ST_done); + } else { + nout << "Unexpected section at top level: '" << _string << "'\n"; + change_state(ST_error); + } + } +} + + + +/** + * Does the DXF processing when we are within some section. + */ +void DXFFile:: +state_section() { + string tail; + + switch (_code) { + case 0: + if (_string == "ENDSEC") { + change_state(ST_top); + } else { + if (_section == SE_entities) { + if (_string == "3DFACE") { + change_entity(EN_3dface); + } else if (_string == "POINT") { + change_entity(EN_point); + } else if (_string == "INSERT") { + change_entity(EN_insert); + } else if (_string == "VERTEX") { + change_entity(EN_vertex); + } else if (_string == "POLYLINE") { + change_entity(EN_polyline); + } else { + change_entity(EN_unknown); + } + } + } + break; + + case 8: + change_layer(_string); + break; + + case 62: // Color. + _color_index = string_to_int(_string, tail); + break; + + default: + break; + } +} + + +/** + * Does the DXF processing when we are reading an entity. + */ +void DXFFile:: +state_entity() { + string tail; + + switch (_code) { + case 0: + state_section(); + break; + + case 8: + change_layer(_string); + break; + + case 10: + _p[0] = string_to_double(_string, tail); + break; + + case 11: + _q[0] = string_to_double(_string, tail); + break; + + case 12: + _r[0] = string_to_double(_string, tail); + break; + + case 13: + _s[0] = string_to_double(_string, tail); + break; + + case 20: + _p[1] = string_to_double(_string, tail); + break; + + case 21: + _q[1] = string_to_double(_string, tail); + break; + + case 22: + _r[1] = string_to_double(_string, tail); + break; + + case 23: + _s[1] = string_to_double(_string, tail); + break; + + case 30: + _p[2] = string_to_double(_string, tail); + break; + + case 31: + _q[2] = string_to_double(_string, tail); + break; + + case 32: + _r[2] = string_to_double(_string, tail); + break; + + case 33: + _s[2] = string_to_double(_string, tail); + break; + + case 62: // Color. + _color_index = string_to_int(_string, tail); + break; + + case 66: // Vertices-follow. + _vertices_follow = (string_to_int(_string, tail) != 0); + break; + + case 70: // Polyline flags. + _flags = string_to_int(_string, tail); + break; + + case 210: + _z[0] = string_to_double(_string, tail); + break; + + case 220: + _z[1] = string_to_double(_string, tail); + break; + + case 230: + _z[2] = string_to_double(_string, tail); + break; + + default: + break; + } +} + + +/** + * Does the DXF processing when we are reading the list of vertices that might + * follow an entity. + */ +void DXFFile:: +state_verts() { + string tail; + + switch (_code) { + case 0: + state_section(); + break; + + case 8: + change_layer(_string); + break; + + case 10: + _p[0] = string_to_double(_string, tail); + break; + + case 20: + _p[1] = string_to_double(_string, tail); + break; + + case 30: + _p[2] = string_to_double(_string, tail); + break; + + default: + break; + } +} + + +ostream &operator << (ostream &out, const DXFFile::State &state) { + switch (state) { + case DXFFile::ST_top: + return out << "ST_top"; + case DXFFile::ST_section: + return out << "ST_section"; + case DXFFile::ST_entity: + return out << "ST_entity"; + case DXFFile::ST_verts: + return out << "ST_verts"; + case DXFFile::ST_error: + return out << "ST_error"; + case DXFFile::ST_done: + return out << "ST_done"; + } + return out << "Unknown state"; +} + +ostream &operator << (ostream &out, const DXFFile::Section §ion) { + switch (section) { + case DXFFile::SE_unknown: + return out << "SE_unknown"; + case DXFFile::SE_header: + return out << "SE_header"; + case DXFFile::SE_tables: + return out << "SE_tables"; + case DXFFile::SE_blocks: + return out << "SE_blocks"; + case DXFFile::SE_entities: + return out << "SE_entities"; + case DXFFile::SE_objects: + return out << "SE_objects"; + } + return out << "Unknown section"; +} + +ostream &operator << (ostream &out, const DXFFile::Entity &entity) { + switch (entity) { + case DXFFile::EN_unknown: + return out << "EN_unknown"; + case DXFFile::EN_3dface: + return out << "EN_3dface"; + case DXFFile::EN_point: + return out << "EN_point"; + case DXFFile::EN_insert: + return out << "EN_insert"; + case DXFFile::EN_vertex: + return out << "EN_vertex"; + case DXFFile::EN_polyline: + return out << "EN_polyline"; + } + return out << "Unknown entity"; +} diff --git a/pandatool/src/dxf/dxfFile.h b/pandatool/src/dxf/dxfFile.h new file mode 100644 index 00000000..e555960e --- /dev/null +++ b/pandatool/src/dxf/dxfFile.h @@ -0,0 +1,168 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfFile.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFFILE_H +#define DXFFILE_H + +#include "pandatoolbase.h" + +#include "dxfLayer.h" +#include "dxfLayerMap.h" +#include "dxfVertex.h" + +#include "luse.h" +#include "filename.h" + + +static const int DXF_max_line = 256; +static const int DXF_num_colors = 256; + +/** + * A generic DXF-reading class. This class can read a DXF file but doesn't + * actually do anything with the data; it's intended to be inherited from and + * the appropriate functions overridden (particularly DoneEntity()). + */ +class DXFFile : public MemoryBase { +public: + DXFFile(); + virtual ~DXFFile(); + + void process(Filename filename); + void process(std::istream *in, bool owns_in); + + // These functions are called as the file is processed. These are the main + // hooks for redefining how the class should dispense its data. As each + // function is called, the state stored in the DXFFile class reflects the + // data that was most recently read. + + virtual void begin_file(); + virtual void begin_section(); + virtual void done_vertex(); + virtual void done_entity(); + virtual void end_section(); + virtual void end_file(); + virtual void error(); + + // new_layer() is called whenever the DXFFile class encounters a new Layer + // definition, and must allocate a DXFLayer instance. This function is + // provided so that user code may force allocate of a specialized DXFLayer + // instance instead. + virtual DXFLayer *new_layer(const std::string &name) { + return new DXFLayer(name); + } + + enum State { + ST_top, + ST_section, + ST_entity, + ST_verts, + ST_error, + ST_done, + }; + enum Section { + SE_unknown, + SE_header, + SE_tables, + SE_blocks, + SE_entities, + SE_objects, + }; + enum Entity { + EN_unknown, + EN_3dface, + EN_point, + EN_insert, + EN_vertex, + EN_polyline, + }; + enum PolylineFlags { + PF_closed = 0x01, + PF_curve_fit = 0x02, + PF_spline_fit = 0x04, + PF_3d = 0x08, + PF_3d_mesh = 0x10, + PF_closed_n = 0x20, + PF_polyface = 0x40, + PF_continuous_linetype = 0x80, + }; + + // This is a table of standard Autocad colors. DXF files can store only a + // limited range of colors; specifically, the 255 colors defined by Autocad. + struct Color { + double r, g, b; + }; + static Color _colors[DXF_num_colors]; + + // find_color() returns the index of the closest matching AutoCAD color to + // the indicated r, g, b. + static int find_color(double r, double g, double b); + + // get_color() returns the r,g,b of the current entity. It is valid at the + // time done_entity() is called. + const Color &get_color() const; + + // Some entities are defined in world coordinates, in 3-d space; other + // entities are inherently 2-d in nature and are defined in planar + // coordinates and must be converted to 3-d space. Call this function from + // done_entity() to convert a 2-d entity to 3-d world coordinates. + void ocs_2_wcs(); + + // These members indicate the current state and describe properties of the + // current thing being processed. They are valid at done_entity(), and at + // other times. + int _flags; + Section _section; + Entity _entity; + LPoint3d _p, _q, _r, _s; + LVector3d _z; + int _color_index; + DXFLayer *_layer; + + // _verts is the list of vertices associated with the current entity. It is + // valid at the time done_entity() is called. + DXFVertices _verts; + + // This is the set of layers encountered within the DXF file. + DXFLayerMap _layers; + +protected: + State _state; + bool _vertices_follow; + LMatrix4d _ocs2wcs; + + std::istream *_in; + bool _owns_in; + + int _code; + std::string _string; + + void compute_ocs(); + + bool get_group(); + void change_state(State new_state); + void change_section(Section new_section); + void change_layer(const std::string &layer_name); + void change_entity(Entity new_entity); + void reset_entity(); + + void state_top(); + void state_section(); + void state_entity(); + void state_verts(); +}; + +std::ostream &operator << (std::ostream &out, const DXFFile::State &state); +std::ostream &operator << (std::ostream &out, const DXFFile::Section §ion); +std::ostream &operator << (std::ostream &out, const DXFFile::Entity &entity); + +#endif diff --git a/pandatool/src/dxf/dxfLayer.cxx b/pandatool/src/dxf/dxfLayer.cxx new file mode 100644 index 00000000..6803cc17 --- /dev/null +++ b/pandatool/src/dxf/dxfLayer.cxx @@ -0,0 +1,29 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfLayer.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfLayer.h" + + +/** + * + */ +DXFLayer:: +DXFLayer(const std::string &name) : Namable(name) { +} + +/** + * + */ +DXFLayer:: +~DXFLayer() { +} diff --git a/pandatool/src/dxf/dxfLayer.h b/pandatool/src/dxf/dxfLayer.h new file mode 100644 index 00000000..74519728 --- /dev/null +++ b/pandatool/src/dxf/dxfLayer.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfLayer.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFLAYER_H +#define DXFLAYER_H + +#include "pandatoolbase.h" +#include "namable.h" + +/** + * This represents a "layer" as read from the DXF file. A layer may be + * defined by reading the header part of the file, or it may be implicitly + * defined by an entity's having referenced it. + * + * User code may derive from DXFLayer to associate private data with each + * layer, if desired. + */ +class DXFLayer : public Namable { +public: + DXFLayer(const std::string &name); + virtual ~DXFLayer(); +}; + +#endif diff --git a/pandatool/src/dxf/dxfLayerMap.cxx b/pandatool/src/dxf/dxfLayerMap.cxx new file mode 100644 index 00000000..d2049c81 --- /dev/null +++ b/pandatool/src/dxf/dxfLayerMap.cxx @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfLayerMap.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfLayerMap.h" +#include "dxfFile.h" + +/** + * Looks up the layer name in the map, and returns a pointer to the associated + * DXFLayer. If this is the first time this layer name has been used, creates + * a new DXFLayer by the given name. In this case, it calls + * dxffile->new_layer() to create the layer, allowing user code to override + * this function to create a specialized time, if desired. + */ +DXFLayer *DXFLayerMap:: +get_layer(const std::string &name, DXFFile *dxffile) { + iterator lmi; + lmi = find(name); + if (lmi != end()) { + // The layer was already here. + return (*lmi).second; + } + + // Need a new layer. + DXFLayer *layer = dxffile->new_layer(name); + (*this)[name] = layer; + + return layer; +} diff --git a/pandatool/src/dxf/dxfLayerMap.h b/pandatool/src/dxf/dxfLayerMap.h new file mode 100644 index 00000000..f2fabe53 --- /dev/null +++ b/pandatool/src/dxf/dxfLayerMap.h @@ -0,0 +1,33 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfLayerMap.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFLAYERMAP_H +#define DXFLAYERMAP_H + +#include "pandatoolbase.h" +#include "pmap.h" + +class DXFLayer; +class DXFFile; + +/** + * A map of string (layer name) to DXFLayer: that is, the layers of a file + * ordered by name. This is used as a lookup within DXFFile to locate the + * layer associated with a particular entity. + */ +class DXFLayerMap : public pmap { +public: + DXFLayer *get_layer(const std::string &name, DXFFile *dxffile); +}; + +#endif diff --git a/pandatool/src/dxf/dxfVertex.cxx b/pandatool/src/dxf/dxfVertex.cxx new file mode 100644 index 00000000..3802802f --- /dev/null +++ b/pandatool/src/dxf/dxfVertex.cxx @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfVertex.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfVertex.h" + +/** + * This defines a unique ordering for vertices so that the DXFVertexMap can + * group identical vertices together. + */ +int DXFVertex:: +operator < (const DXFVertex &other) const { + if (fabs(_p[0] - other._p[0]) > 0.0001) { + return _p[0] < other._p[0]; + } else if (fabs(_p[1] - other._p[1]) > 0.0001) { + return _p[1] < other._p[1]; + } else if (fabs(_p[2] - other._p[2]) > 0.0001) { + return _p[2] < other._p[2]; + } + + return false; +} diff --git a/pandatool/src/dxf/dxfVertex.h b/pandatool/src/dxf/dxfVertex.h new file mode 100644 index 00000000..32494509 --- /dev/null +++ b/pandatool/src/dxf/dxfVertex.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfVertex.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFVERTEX_H +#define DXFVERTEX_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "luse.h" + +/** + * Stored within DXFFile, this is the basic Vertex data of a DXF file. When + * DXFFile::DoneEntity() is called, if the entity is a type to have vertices, + * then DXFFile::_verts contains a list of all the vertices that belong to the + * entity. + */ +class DXFVertex { +public: + DXFVertex() { } + DXFVertex(const LPoint3d &p) : _p(p) { } + int operator < (const DXFVertex &other) const; + + LPoint3d _p; +}; + +typedef pvector DXFVertices; + +#endif diff --git a/pandatool/src/dxf/p3dxf_composite1.cxx b/pandatool/src/dxf/p3dxf_composite1.cxx new file mode 100644 index 00000000..6d62c98c --- /dev/null +++ b/pandatool/src/dxf/p3dxf_composite1.cxx @@ -0,0 +1,4 @@ +#include "dxfFile.cxx" +#include "dxfLayer.cxx" +#include "dxfLayerMap.cxx" +#include "dxfVertex.cxx" diff --git a/pandatool/src/dxfegg/CMakeLists.txt b/pandatool/src/dxfegg/CMakeLists.txt new file mode 100644 index 00000000..74684b1c --- /dev/null +++ b/pandatool/src/dxfegg/CMakeLists.txt @@ -0,0 +1,19 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3DXFEGG_HEADERS + dxfToEggConverter.h + dxfToEggLayer.h +) + +set(P3DXFEGG_SOURCES + dxfToEggConverter.cxx + dxfToEggLayer.cxx +) + +add_library(p3dxfegg STATIC ${P3DXFEGG_HEADERS} ${P3DXFEGG_SOURCES}) +target_link_libraries(p3dxfegg p3dxf p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/dxfegg/dxfToEggConverter.cxx b/pandatool/src/dxfegg/dxfToEggConverter.cxx new file mode 100644 index 00000000..6ba64119 --- /dev/null +++ b/pandatool/src/dxfegg/dxfToEggConverter.cxx @@ -0,0 +1,145 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEggConverter.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfToEggConverter.h" +#include "dxfToEggLayer.h" +#include "eggData.h" + +/** + * + */ +DXFToEggConverter:: +DXFToEggConverter() { +} + +/** + * + */ +DXFToEggConverter:: +DXFToEggConverter(const DXFToEggConverter ©) : + SomethingToEggConverter(copy) +{ +} + +/** + * + */ +DXFToEggConverter:: +~DXFToEggConverter() { +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *DXFToEggConverter:: +make_copy() { + return new DXFToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +std::string DXFToEggConverter:: +get_name() const { + return "DXF"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +std::string DXFToEggConverter:: +get_extension() const { + return "dxf"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool DXFToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + */ +bool DXFToEggConverter:: +convert_file(const Filename &filename) { + clear_error(); + + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_zup_right); + } + + process(filename); + return !had_error(); +} + +/** + * + */ +DXFLayer *DXFToEggConverter:: +new_layer(const std::string &name) { + return new DXFToEggLayer(name, get_egg_data()); +} + +/** + * If the entity is a polygon, creates the corresponding egg polygon. + */ +void DXFToEggConverter:: +done_entity() { + if (_entity == EN_polyline) { + // A Polyline is either an unclosed series of connected line segments, or + // a closed polygon of arbitrary complexity. + + if ((_flags & PF_3d) == 0) { + // it's a 2-d polygon; convert it to 3-d coordinates. + ocs_2_wcs(); + } + + if (_flags & PF_closed) { + // it's closed; create a polygon. + nassertv(_layer!=nullptr); + ((DXFToEggLayer *)_layer)->add_polygon(this); + } else { + // It's open; create a series of line segments. + nassertv(_layer!=nullptr); + ((DXFToEggLayer *)_layer)->add_line(this); + } + + } else if (_entity == EN_3dface) { + // DXF can also represent a polygon as a 3DFace. This might be either a + // quad or a triangle (if two of the vertices are the same). We'll add + // the vertices to our list of vertices and then define the polygon. + _verts.clear(); + _verts.push_back(DXFVertex(_s)); + _verts.push_back(DXFVertex(_r)); + _verts.push_back(DXFVertex(_q)); + _verts.push_back(DXFVertex(_p)); + + nassertv(_layer!=nullptr); + ((DXFToEggLayer *)_layer)->add_polygon(this); + } +} + +/** + * A hook for user code, if desired. This function is called when some + * unexpected error occurs while reading the DXF file. + */ +void DXFToEggConverter:: +error() { + _error = true; +} diff --git a/pandatool/src/dxfegg/dxfToEggConverter.h b/pandatool/src/dxfegg/dxfToEggConverter.h new file mode 100644 index 00000000..56969e74 --- /dev/null +++ b/pandatool/src/dxfegg/dxfToEggConverter.h @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEggConverter.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFTOEGGCONVERTER_H +#define DXFTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "somethingToEggConverter.h" +#include "dxfFile.h" + +/** + * This class supervises the construction of an EggData structure from a DXF + * file. + */ +class DXFToEggConverter : public SomethingToEggConverter, public DXFFile { +public: + DXFToEggConverter(); + DXFToEggConverter(const DXFToEggConverter ©); + ~DXFToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + + virtual bool convert_file(const Filename &filename); + +protected: + virtual DXFLayer *new_layer(const std::string &name); + virtual void done_entity(); + virtual void error(); + + bool _error; +}; + +#endif diff --git a/pandatool/src/dxfegg/dxfToEggLayer.cxx b/pandatool/src/dxfegg/dxfToEggLayer.cxx new file mode 100644 index 00000000..3ecb915e --- /dev/null +++ b/pandatool/src/dxfegg/dxfToEggLayer.cxx @@ -0,0 +1,99 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEggLayer.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfToEggLayer.h" +#include "dxfToEggConverter.h" + +#include "dxfFile.h" +#include "eggGroup.h" +#include "eggPolygon.h" +#include "eggLine.h" +#include "eggVertex.h" +#include "eggVertexPool.h" + + +/** + * + */ +DXFToEggLayer:: +DXFToEggLayer(const std::string &name, EggGroupNode *parent) : DXFLayer(name) { + _group = new EggGroup(name); + parent->add_child(_group); + _vpool = new EggVertexPool(name); + _group->add_child(_vpool); +} + + +/** + * Given that done_entity() has just been called and that the current entity + * represents a polygon, adds the corresponding polygon to the layer's + * EggGroup and vertex pool. + */ +void DXFToEggLayer:: +add_polygon(const DXFToEggConverter *entity) { + EggPolygon *poly = new EggPolygon; + _group->add_child(poly); + + const DXFFile::Color &color = entity->get_color(); + poly->set_color(LColor(color.r, color.g, color.b, 1.0)); + + // A polyline's vertices are stored in the attached vector by dxf.cxx. They + // were defined in the DXF file using a series of "VERTEX" entries. + + // For a 3dface, the vertices are defined explicitly as part of the entity; + // but in this case, they were added to the vector before add_polygon() was + // called. + + DXFVertices::const_iterator vi; + for (vi = entity->_verts.begin(); + vi != entity->_verts.end(); + ++vi) { + poly->add_vertex(add_vertex(*vi)); + } + + poly->cleanup(); +} + + +/** + * Similar to add_polygon(), but adds a set of point lights instead. + */ +void DXFToEggLayer:: +add_line(const DXFToEggConverter *entity) { + EggLine *line = new EggLine; + _group->add_child(line); + + const DXFFile::Color &color = entity->get_color(); + line->set_color(LColor(color.r, color.g, color.b, 1.0)); + + DXFVertices::const_iterator vi; + for (vi = entity->_verts.begin(); + vi != entity->_verts.end(); + ++vi) { + line->add_vertex(add_vertex(*vi)); + } +} + + +/** + * Adds a unique vertex to the layer's vertex pool and returns it. If the + * vertex was already defined previously, returns the original definition. + * This is designed to share the common vertices within a layer. + */ +EggVertex *DXFToEggLayer:: +add_vertex(const DXFVertex &vert) { + EggVertex egg_vert; + egg_vert.set_pos(vert._p); + + return _vpool->create_unique_vertex(egg_vert); +} diff --git a/pandatool/src/dxfegg/dxfToEggLayer.h b/pandatool/src/dxfegg/dxfToEggLayer.h new file mode 100644 index 00000000..9067cc70 --- /dev/null +++ b/pandatool/src/dxfegg/dxfToEggLayer.h @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEggLayer.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFTOEGGLAYER_H +#define DXFTOEGGLAYER_H + +#include "pandatoolbase.h" + +#include "dxfLayer.h" +#include "eggVertexPool.h" +#include "eggGroup.h" +#include "pointerTo.h" + +class EggGroupNode; +class EggVertex; +class DXFVertex; +class DXFToEggConverter; + +/** + * The specialization of DXFLayer used by DXFToEggConverter. It contains a + * pointer to an EggGroup and a vertex pool; these are used to build up + * polygons grouped by layer in the egg file as each polygon is read from the + * DXF file. + */ +class DXFToEggLayer : public DXFLayer { +public: + DXFToEggLayer(const std::string &name, EggGroupNode *parent); + + void add_polygon(const DXFToEggConverter *entity); + void add_line(const DXFToEggConverter *entity); + EggVertex *add_vertex(const DXFVertex &vertex); + + PT(EggVertexPool) _vpool; + PT(EggGroup) _group; +}; + + +#endif diff --git a/pandatool/src/dxfprogs/CMakeLists.txt b/pandatool/src/dxfprogs/CMakeLists.txt new file mode 100644 index 00000000..0a2033d1 --- /dev/null +++ b/pandatool/src/dxfprogs/CMakeLists.txt @@ -0,0 +1,19 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(dxf-points dxfPoints.cxx dxfPoints.h) +target_link_libraries(dxf-points p3progbase p3dxf) +install(TARGETS dxf-points EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(egg2dxf eggToDXF.cxx eggToDXF.h eggToDXFLayer.cxx eggToDXFLayer.h) + target_link_libraries(egg2dxf p3dxfegg p3eggbase p3progbase) + install(TARGETS egg2dxf EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(dxf2egg dxfToEgg.cxx dxfToEgg.h) + target_link_libraries(dxf2egg p3dxfegg p3eggbase p3progbase) + install(TARGETS dxf2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/dxfprogs/dxfPoints.cxx b/pandatool/src/dxfprogs/dxfPoints.cxx new file mode 100644 index 00000000..c0002b28 --- /dev/null +++ b/pandatool/src/dxfprogs/dxfPoints.cxx @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfPoints.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfPoints.h" + +/** + * + */ +DXFPoints:: +DXFPoints() : + WithOutputFile(true, true, false) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = ".txt"; + + set_program_brief("extract points from AutoCAD .dxf files"); + set_program_description + ("This program reads an AutoCAD .dxf file and generates a simple " + "list of all the points contained within it, one per line, to a " + "text file, or to standard output."); + + clear_runlines(); + add_runline("[opts] input.dxf > output.txt"); + add_runline("[opts] -o output.txt input.dxf"); + add_runline("[opts] input.dxf output.txt"); +} + + +/** + * + */ +void DXFPoints:: +run() { + // Invoke the DXFFile base class to process the input file. + process(_input_filename); +} + +/** + * This is inherited from DXFFile, and gets called as each entity (face, line, + * whatever) has finished processing. + */ +void DXFPoints:: +done_entity() { + if (_entity == EN_point) { + get_output() << _p << "\n"; + + } else if (_entity == EN_insert) { + ocs_2_wcs(); + get_output() << _p << "\n"; + } +} + +/** + * + */ +bool DXFPoints:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the .dxf file to read on the command line.\n"; + return false; + + } else if (args.size() != 1) { + nout << "You must specify only one .dxf file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int main(int argc, char *argv[]) { + DXFPoints prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/dxfprogs/dxfPoints.h b/pandatool/src/dxfprogs/dxfPoints.h new file mode 100644 index 00000000..9715b5d6 --- /dev/null +++ b/pandatool/src/dxfprogs/dxfPoints.h @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfPoints.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFPOINTS_H +#define DXFPOINTS_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "withOutputFile.h" + +#include "dxfFile.h" + +/** + * A simple program to read a dxf file and list the points contained within it + * to a text file. + */ +class DXFPoints : public ProgramBase, public WithOutputFile, public DXFFile { +public: + DXFPoints(); + + void run(); + + virtual void done_entity(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; +}; + +#endif diff --git a/pandatool/src/dxfprogs/dxfToEgg.cxx b/pandatool/src/dxfprogs/dxfToEgg.cxx new file mode 100644 index 00000000..4ca66f0a --- /dev/null +++ b/pandatool/src/dxfprogs/dxfToEgg.cxx @@ -0,0 +1,74 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEgg.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "dxfToEgg.h" + +#include "dxfToEggConverter.h" + +/** + * + */ +DXFToEgg:: +DXFToEgg() : + SomethingToEgg("DXF", ".dxf") +{ + add_units_options(); + add_normals_options(); + add_transform_options(); + + set_program_brief("convert AutoCAD .dxf files to .egg files"); + set_program_description + ("This program converts DXF (AutoCAD interchange format) to egg. It " + "only converts polygon data, with no fancy tricks. DXF does not support " + "hierarchical databases, so dxf2egg creates a single group at the root " + "level for each layer in the DXF file."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is z-up."); + + _coordinate_system = CS_zup_right; +} + +/** + * + */ +void DXFToEgg:: +run() { + nout << "Reading " << _input_filename << "\n"; + + _data->set_coordinate_system(_coordinate_system); + + DXFToEggConverter converter; + converter.set_egg_data(_data); + converter._allow_errors = _allow_errors; + + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + DXFToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/dxfprogs/dxfToEgg.h b/pandatool/src/dxfprogs/dxfToEgg.h new file mode 100644 index 00000000..cb8b5b6b --- /dev/null +++ b/pandatool/src/dxfprogs/dxfToEgg.h @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file dxfToEgg.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef DXFTOEGG_H +#define DXFTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "dxfToEggConverter.h" + +/** + * A program to read a DXF file and generate an egg file. + */ +class DXFToEgg : public SomethingToEgg { +public: + DXFToEgg(); + + void run(); +}; + +#endif diff --git a/pandatool/src/dxfprogs/eggToDXF.cxx b/pandatool/src/dxfprogs/eggToDXF.cxx new file mode 100644 index 00000000..412b8156 --- /dev/null +++ b/pandatool/src/dxfprogs/eggToDXF.cxx @@ -0,0 +1,149 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDXF.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "eggToDXF.h" +#include "eggPolygon.h" +#include "dcast.h" + +/** + * + */ +EggToDXF:: +EggToDXF() : + EggToSomething("DXF", ".dxf", true, false) +{ + set_binary_output(true); + set_program_brief("convert .egg files to AutoCAD .dxf files"); + set_program_description + ("This program converts files from egg format to AutoCAD DXF format. " + "Since DXF does not support nested hierarchies, vertex normals, or any " + "fancy stuff you are probably used to, there is some information lost " + "in the conversion"); + + add_option + ("p", "", 0, + "Use POLYLINE to represent polygons instead of the default, 3DFACE.", + &EggToDXF::dispatch_none, &_use_polyline); + + _coordinate_system = CS_zup_right; + _got_coordinate_system = true; +} + +/** + * + */ +void EggToDXF:: +run() { + get_layers(_data); + if (_layers.empty()) { + nout << "Egg file contains no polygons. Output file not written.\n"; + exit(1); + } + + // uniquify_names("layer", _layers.begin(), _layers.end()); + + std::ostream &out = get_output(); + + // Autodesk says we don't need the header, but some DXF-reading programs + // might get confused if it's missing. We'll write an empty header. + out << "0\nSECTION\n" + << "2\nHEADER\n" + << "0\nENDSEC\n"; + + write_tables(out); + write_entities(out); + out << "0\nEOF\n"; // Mark end of file. + + if (!out) { + nout << "An error occurred while writing.\n"; + exit(1); + } +} + +/** + * Traverses the hierarchy, looking for groups that contain polygons. Any + * such groups are deemed to be layers, and are added to the layers set. + */ +void EggToDXF:: +get_layers(EggGroupNode *group) { + bool has_polys = false; + + EggToDXFLayer layer(this, group); + + EggGroupNode::iterator ci; + for (ci = group->begin(); ci != group->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggPolygon::get_class_type())) { + EggPolygon *poly = DCAST(EggPolygon, child); + has_polys = true; + + layer.add_color(poly->get_color()); + + } else if (child->is_of_type(EggGroupNode::get_class_type())) { + get_layers(DCAST(EggGroupNode, child)); + } + } + + if (has_polys) { + layer.choose_overall_color(); + _layers.push_back(layer); + } +} + + +/** + * Writes out the "layers", e.g. groups. This is just the layers definition + * in the tables section at the beginning of the file; the actual geometry + * gets written later, in write_entities(). + */ +void EggToDXF:: +write_tables(std::ostream &out) { + out << "0\nSECTION\n" + << "2\nTABLES\n" // Begin TABLES section. + << "0\nTABLE\n" + << "2\nLAYER\n" // Define LAYERS. + << "70\n" << _layers.size() << "\n"; + + EggToDXFLayers::iterator li; + for (li = _layers.begin(); li != _layers.end(); ++li) { + (*li).write_layer(out); + } + + out << "0\nENDTAB\n" // End LAYERS definition. + << "0\nENDSEC\n"; // End TABLES section. +} + +/** + * Writes out the "entities", e.g. polygons, defined for all layers. + */ +void EggToDXF:: +write_entities(std::ostream &out) { + out << "0\nSECTION\n" + << "2\nENTITIES\n"; // Begin ENTITIES section. + + EggToDXFLayers::iterator li; + for (li = _layers.begin(); li != _layers.end(); ++li) { + (*li).write_entities(out); + } + + out << "0\nENDSEC\n"; // End ENTITIES section. +} + + + +int main(int argc, char *argv[]) { + EggToDXF prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/dxfprogs/eggToDXF.h b/pandatool/src/dxfprogs/eggToDXF.h new file mode 100644 index 00000000..da7764fb --- /dev/null +++ b/pandatool/src/dxfprogs/eggToDXF.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDXF.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef EGGTODXF_H +#define EGGTODXF_H + +#include "pandatoolbase.h" + +#include "eggToSomething.h" +#include "eggToDXFLayer.h" + +class EggGroupNode; + +/** + * A program to read an egg file and write a DXF file. + */ +class EggToDXF : public EggToSomething { +public: + EggToDXF(); + + void run(); + + bool _use_polyline; + +private: + void get_layers(EggGroupNode *group); + void write_tables(std::ostream &out); + void write_entities(std::ostream &out); + + EggToDXFLayers _layers; +}; + +#endif diff --git a/pandatool/src/dxfprogs/eggToDXFLayer.cxx b/pandatool/src/dxfprogs/eggToDXFLayer.cxx new file mode 100644 index 00000000..6922f0b8 --- /dev/null +++ b/pandatool/src/dxfprogs/eggToDXFLayer.cxx @@ -0,0 +1,219 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDXFLayer.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "eggToDXFLayer.h" +#include "eggToDXF.h" +#include "dxfFile.h" +#include "eggGroup.h" +#include "eggGroupNode.h" +#include "eggPolygon.h" +#include "dcast.h" + +using std::ostream; + +/** + * + */ +EggToDXFLayer:: +EggToDXFLayer(EggToDXF *egg2dxf, EggGroupNode *group) : + _egg2dxf(egg2dxf), _group(group) +{ + _layer_color = -1; +} + +/** + * + */ +EggToDXFLayer:: +EggToDXFLayer(const EggToDXFLayer ©) : + _egg2dxf(copy._egg2dxf), + _group(copy._group), + _layer_color(copy._layer_color) +{ + // The copy constructor doesn't bother with the ColorCounts. +} + +/** + * + */ +void EggToDXFLayer:: +operator = (const EggToDXFLayer ©) { + _egg2dxf = copy._egg2dxf; + _group = copy._group; + _layer_color = copy._layer_color; + + // The copy constructor doesn't bother with the ColorCounts. +} + +/** + * Records that one polygon is defined using the indicated color. This will + * get accumulated; the color used by the majority of polygons will become the + * layer color. + */ +void EggToDXFLayer:: +add_color(const LColor &color) { + int autocad_color = get_autocad_color(color); + + ColorCounts::iterator cci; + cci = _color_counts.find(autocad_color); + if (cci == _color_counts.end()) { + // The first time a particular color was used. Count it once. + _color_counts[autocad_color] = 1; + } else { + // This color has been used before. Count it again. + (*cci).second++; + } +} + + +/** + * After all polygons have been accounted for, chooses the polygon color that + * occurred most often as the layer color. + */ +void EggToDXFLayer:: +choose_overall_color() { + int max_count = 0; + + ColorCounts::iterator cci; + for (cci = _color_counts.begin(); cci != _color_counts.end(); ++cci) { + int count = (*cci).second; + if (count > max_count) { + _layer_color = (*cci).first; + max_count = count; + } + } +} + + +/** + * Writes the layer definition into the table at the beginning of the DXF + * file. This does not write the actual geometry; that gets done later by + * write_entities(). + */ +void EggToDXFLayer:: +write_layer(ostream &out) { + out << "0\nLAYER\n" + << "2\n" << _group->get_name() << "\n" + << "70\n0\n" + << "62\n" << _layer_color << "\n" + << "6\nCONTINUOUS\n"; +} + +/** + * Writes a polygon as a POLYLINE entity. + */ +void EggToDXFLayer:: +write_polyline(EggPolygon *poly, ostream &out) { + out << "0\nPOLYLINE\n" + << "8\n" << _group->get_name() << "\n" + << "66\n1\n" + << "70\n1\n" + << "62\n" << get_autocad_color(poly->get_color()) << "\n"; + + // Since DXF uses a clockwise ordering convention, we must reverse the order + // in which we write out the vertices. + EggPolygon::reverse_iterator vi; + for (vi = poly->rbegin(); vi != poly->rend(); ++vi) { + EggVertex *vtx = (*vi); + LVecBase3d pos = vtx->get_pos3() * _group->get_vertex_frame(); + out << "0\nVERTEX\n" + << "10\n" << pos[0] << "\n" + << "20\n" << pos[1] << "\n" + << "30\n" << pos[2] << "\n"; + } + out << "0\nSEQEND\n"; +} + +/** + * Writes a polygon as a 3DFACE entity. + */ +void EggToDXFLayer:: +write_3d_face(EggPolygon *poly, ostream &out) { + if (poly->size() > 4) { + // If we have a big polygon, we have to triangulate it, since 3DFaces can + // only be tris and quads. + PT(EggGroup) group = new EggGroup; + poly->triangulate_into(group, true); + + EggGroupNode::iterator ci; + for (ci = group->begin(); ci != group->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggPolygon::get_class_type())) { + write_3d_face(DCAST(EggPolygon, child), out); + } + } + + } else if (poly->size() > 2) { + // Otherwise, if we have a tri or a quad, just write it out. + out << "0\n3DFACE\n" + << "8\n" << _group->get_name() << "\n"; + + // Since DXF uses a clockwise ordering convention, we must reverse the + // order in which we write out the vertices. + int i; + EggPolygon::reverse_iterator vi; + for (i = 0, vi = poly->rbegin(); vi != poly->rend(); ++i, ++vi) { + EggVertex *vtx = (*vi); + LVecBase3d pos = vtx->get_pos3() * _group->get_vertex_frame(); + out << 10 + i << "\n" << pos[0] << "\n" + << 20 + i << "\n" << pos[1] << "\n" + << 30 + i << "\n" << pos[2] << "\n"; + if (i == 2 && poly->size() == 3) { + // A special case for triangles: repeat the last vertex. + out << 11 + i << "\n" << pos[0] << "\n" + << 21 + i << "\n" << pos[1] << "\n" + << 31 + i << "\n" << pos[2] << "\n"; + } + } + } +} + + +/** + * Writes out the "entities", e.g. polygons, defined for the current layer. + */ +void EggToDXFLayer:: +write_entities(ostream &out) { + EggGroupNode::iterator ci; + for (ci = _group->begin(); ci != _group->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggPolygon::get_class_type())) { + EggPolygon *poly = DCAST(EggPolygon, child); + if (_egg2dxf->_use_polyline) { + write_polyline(poly, out); + } else { + write_3d_face(poly, out); + } + } + } +} + +/** + * Returns the AutoCAD color index that most closely matches the indicated + * EggColor. + */ +int EggToDXFLayer:: +get_autocad_color(const LColor &color) { + typedef pmap ColorMap; + static ColorMap _map; + + ColorMap::iterator cmi; + cmi = _map.find(color); + if (cmi != _map.end()) { + return (*cmi).second; + } + + int result = DXFFile::find_color(color[0], color[1], color[2]); + _map[color] = result; + return result; +} diff --git a/pandatool/src/dxfprogs/eggToDXFLayer.h b/pandatool/src/dxfprogs/eggToDXFLayer.h new file mode 100644 index 00000000..a5fe7b75 --- /dev/null +++ b/pandatool/src/dxfprogs/eggToDXFLayer.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToDXFLayer.h + * @author drose + * @date 2004-05-04 + */ + +#ifndef EGGTODXFLAYER_H +#define EGGTODXFLAYER_H + +#include "pandatoolbase.h" +#include "pmap.h" +#include "pvector.h" +#include "luse.h" + +class EggToDXF; +class EggPolygon; +class EggGroupNode; + +/** + * A single layer in the DXF file to be written by EggToDXF. + */ +class EggToDXFLayer { +public: + EggToDXFLayer(EggToDXF *egg2dxf, EggGroupNode *group); + EggToDXFLayer(const EggToDXFLayer ©); + void operator = (const EggToDXFLayer ©); + + void add_color(const LColor &color); + void choose_overall_color(); + + void write_layer(std::ostream &out); + void write_polyline(EggPolygon *poly, std::ostream &out); + void write_3d_face(EggPolygon *poly, std::ostream &out); + void write_entities(std::ostream &out); + +private: + int get_autocad_color(const LColor &color); + + typedef pmap ColorCounts; + ColorCounts _color_counts; + + EggToDXF *_egg2dxf; + EggGroupNode *_group; + int _layer_color; +}; + +typedef pvector EggToDXFLayers; + +#endif diff --git a/pandatool/src/egg-mkfont/CMakeLists.txt b/pandatool/src/egg-mkfont/CMakeLists.txt new file mode 100644 index 00000000..60f0331e --- /dev/null +++ b/pandatool/src/egg-mkfont/CMakeLists.txt @@ -0,0 +1,25 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG OR NOT HAVE_FREETYPE) + return() +endif() + +set(P3EGG_MKFONT_HEADERS + eggMakeFont.h + rangeDescription.h rangeDescription.I + rangeIterator.h rangeIterator.I +) + +set(P3EGG_MKFONT_SOURCES + eggMakeFont.cxx + rangeDescription.cxx + rangeIterator.cxx +) + +composite_sources(egg-mkfont P3EGG_MKFONT_SOURCES) +add_executable(egg-mkfont ${P3EGG_MKFONT_HEADERS} ${P3EGG_MKFONT_SOURCES}) +target_link_libraries(egg-mkfont p3palettizer p3eggbase p3pandatoolbase) + +install(TARGETS egg-mkfont EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/egg-mkfont/egg-mkfont_composite1.cxx b/pandatool/src/egg-mkfont/egg-mkfont_composite1.cxx new file mode 100644 index 00000000..aa44d511 --- /dev/null +++ b/pandatool/src/egg-mkfont/egg-mkfont_composite1.cxx @@ -0,0 +1,5 @@ + +#include "eggMakeFont.cxx" +#include "rangeDescription.cxx" +#include "rangeIterator.cxx" + diff --git a/pandatool/src/egg-mkfont/eggMakeFont.cxx b/pandatool/src/egg-mkfont/eggMakeFont.cxx new file mode 100644 index 00000000..4efa4a5a --- /dev/null +++ b/pandatool/src/egg-mkfont/eggMakeFont.cxx @@ -0,0 +1,745 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeFont.cxx + * @author drose + * @date 2001-02-16 + */ + +#include "eggMakeFont.h" +#include "rangeIterator.h" +#include "palettizer.h" +#include "filenameUnifier.h" +#include "eggFile.h" +#include "textureImage.h" +#include "sourceTextureImage.h" +#include "pnmTextMaker.h" +#include "pnmTextGlyph.h" +#include "eggData.h" +#include "eggGroup.h" +#include "eggPoint.h" +#include "eggPolygon.h" +#include "eggTexture.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "string_utils.h" +#include "dcast.h" + +#include + +using std::string; + +/** + * + */ +EggMakeFont:: +EggMakeFont() : EggWriter(true, false) { + set_program_brief("generates .egg files with rasterized font glyphs"); + set_program_description + ("egg-mkfont uses the FreeType library to generate an egg file " + "and a series of texture images from a font file " + "input, such as a TTF file. The resulting egg file " + "can be loaded in Panda as a font for rendering text, even " + "if FreeType is not compiled into the executing Panda.\n\n" + + "egg-mkfont will normally run the generated egg file through " + "egg-palettize automatically as part of the generation process. " + "This collects the individual glyph textures into a small number " + "of texture maps. If you intend to run the font through egg-palettize " + "yourself later, you may choose to omit this step."); + + clear_runlines(); + add_runline("[opts] -o output.egg font"); + add_runline("[opts] font output.egg"); + + add_option + ("fg", "r,g,b[,a]", 0, + "Specifies the foreground color of the generated texture map. The " + "default is white: 1,1,1,1, which leads to the most flexibility " + "as the color can be modulated at runtime to any suitable color.", + &EggMakeFont::dispatch_color, nullptr, &_fg[0]); + + add_option + ("bg", "r,g,b[,a]", 0, + "Specifies the background color of the generated texture map. The " + "default is transparent: 1,1,1,0, which allows the text to be " + "visible against any color background by placing a polygon of a " + "suitable color behind it. If the alpha component of either -fg " + "or -bg is not 1, the generated texture images will include an " + "alpha component; if both colors specify an alpha component of 1 " + "(or do not specify an alpha compenent), then the generated images " + "will not include an alpha component.", + &EggMakeFont::dispatch_color, nullptr, &_bg[0]); + + add_option + ("interior", "r,g,b[,a]", 0, + "Specifies the color to render the interior part of a hollow font. " + "This is a special effect that involves analysis of the bitmap after " + "the font has been rendered, and so is more effective when the pixel " + "size is large. It also implies -noaa (but you can use a scale " + "factor with -sf to achieve antialiasing).", + &EggMakeFont::dispatch_color, &_got_interior, &_interior[0]); + + add_option + ("chars", "range", 0, + "Specifies the characters of the font that are used. The range " + "specification may include combinations of decimal or hex unicode " + "values (where hex values are identified with a leading 0x), separated " + "by commas and hyphens to indicate ranges, e.g. '32-126,0xfa0-0xfff'. " + "It also may specify ranges of ASCII characters by enclosing them " + "within square brackets, e.g. '[A-Za-z0-9]'. If this is not specified, " + "the default set has all ASCII characters and an assorted set of " + "latin-1 characters, diacritics and punctuation marks.", + &EggMakeFont::dispatch_range, nullptr, &_range); + + add_option + ("extra", "file.egg", 0, + "Specifies additional externally-painted glyphs to mix into the " + "generated egg file. The named egg file is expected to contain one " + "or more groups, each of which is named with the decimal unicode " + "number of a character and should contain one polygon. These groups " + "are simply copied into the output egg file as if they were generated " + "locally. This option may be repeated.", + &EggMakeFont::dispatch_vector_string, nullptr, &_extra_filenames); + + add_option + ("ppu", "pixels", 0, + "Specify the pixels per unit. This is the number of pixels in the " + "generated texture map that are used for each onscreen unit (or each " + "10 points of font; see -ps). Setting this number larger results in " + "an easier-to-read font, but at the cost of more texture memory. " + "The default is 40.", + &EggMakeFont::dispatch_double, nullptr, &_pixels_per_unit); + + add_option + ("ps", "size", 0, + "Specify the point size of the resulting font. This controls the " + "apparent size of the font when it is rendered onscreen. By convention, " + "a 10 point font is 1 screen unit high, so the default is 10.", + &EggMakeFont::dispatch_double, nullptr, &_point_size); + + add_option + ("sdf", "", 0, + "If this is set, a signed distance field will be generated, which " + "results in crisp text even when the text is enlarged or zoomed in.", + &EggMakeFont::dispatch_true, nullptr, &_generate_distance_field); + + add_option + ("pm", "n", 0, + "The number of extra pixels around a single character in the " + "generated polygon. This may be a floating-point number. The " + "default is 1.", + &EggMakeFont::dispatch_double, nullptr, &_poly_margin); + + add_option + ("tm", "n", 0, + "The number of extra pixels around each character in the texture map. " + "This may only be an integer. The default is 2. This is meaningful " + "when -nopal is also used; in the normal case, use -pm to control " + "both the polygon size and the texture map spacing.", + &EggMakeFont::dispatch_int, nullptr, &_tex_margin); + + add_option + ("rm", "n", 0, + "The amount of padding in screen units to place around the glyph when " + "rendered. This differs from -pm in that it has no effect on the " + "generated texture map, only on the generated egg. Use this in order to " + "space the characters out in case they appear to be too close together " + "when rendered. The default is 0.", + &EggMakeFont::dispatch_double, nullptr, &_render_margin); + + add_option + ("sf", "factor", 0, + "The scale factor of the generated image. This is the factor by which " + "the font image is generated oversized, then reduced to its final size, " + "to improve antialiasing. If the specified font contains one " + "or more fixed-size fonts instead of a scalable font, the scale factor " + "may be automatically adjusted as necessary to scale the closest-" + "matching font to the desired pixel size. The default is 2.", + &EggMakeFont::dispatch_double, &_got_scale_factor, &_scale_factor); + + add_option + ("noaa", "", 0, + "Disable low-level antialiasing by the Freetype library. " + "This is unrelated to the antialiasing that is applied due to the " + "scale factor specified by -sf; you may have either one, neither, or " + "both kinds of antialiasing enabled.", + &EggMakeFont::dispatch_none, &_no_native_aa); + + add_option + ("nopal", "", 0, + "Don't run egg-palettize automatically on the output file, but " + "just output the raw egg file and all of its individual texture " + "images, one for each glyph.", + &EggMakeFont::dispatch_none, &_no_palettize); + + add_option + ("nr", "", 0, + "Don't actually reduce the images after applying the scale factor, but " + "leave them at their inflated sizes. Presumably you will reduce " + "them later, for instance with egg-palettize.", + &EggMakeFont::dispatch_none, &_no_reduce); + + add_option + ("gp", "pattern", 0, + "The pattern to be used to generate the glyph texture images. This " + "string will be passed to sprintf to generate the actual file name; it " + "should contain the string %d or %x (or some variant such as %03d) " + "which will be filled in with the Unicode number of each symbol. " + "If it is omitted, the default is based on the name of the egg file. " + "This is used only if -nopal is specified; in the normal case, " + "without -nopal, use -pp instead.", + &EggMakeFont::dispatch_string, nullptr, &_output_glyph_pattern); + + add_option + ("pp", "pattern", 0, + "The pattern to be used to generate the palette texture images. This " + "string is effectively passed to egg-palettize as the -tn option, and " + "thus should contain %i for the palette index number. This is used " + "if -nopal is not specified.", + &EggMakeFont::dispatch_string, nullptr, &_output_palette_pattern); + + add_option + ("palsize", "xsize,ysize", 0, + "Specify the size of the palette texture images. This is used if " + "-nopal is not specified.", + &EggMakeFont::dispatch_int_pair, nullptr, _palette_size); + + add_option + ("face", "index", 0, + "Specify the face index of the particular face within the font file " + "to use. Some font files contain multiple faces, indexed beginning " + "at 0. The default is face 0.", + &EggMakeFont::dispatch_int, nullptr, &_face_index); + + _fg.set(1.0, 1.0, 1.0, 1.0); + _bg.set(1.0, 1.0, 1.0, 0.0); + _interior.set(1.0, 1.0, 1.0, 1.0); + _pixels_per_unit = 40.0; + _point_size = 10.0; + _poly_margin = 1.0; + _tex_margin = 2; + _render_margin = 0.0; + _palette_size[0] = _palette_size[1] = 512; + _face_index = 0; + _generate_distance_field = false; + + _text_maker = nullptr; + _vpool = nullptr; + _group = nullptr; +} + + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggMakeFont:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "Must specify name of font file on command line.\n"; + return false; + } + + _input_font_filename = args[0]; + args.pop_front(); + return EggWriter::handle_args(args); +} + +/** + * + */ +void EggMakeFont:: +run() { + if (has_output_filename() && !get_output_filename().get_dirname().empty()) { + FilenameUnifier::set_rel_dirname(get_output_filename().get_dirname()); + } else { + FilenameUnifier::set_rel_dirname("."); + } + + _text_maker = new PNMTextMaker(_input_font_filename, _face_index); + if (!_text_maker->is_valid()) { + exit(1); + } + + if (_got_interior) { + _no_native_aa = true; + } + + if (!_got_scale_factor) { + // The default scale factor is 4 if we are not using FreeType's antialias, + // or 2 if we are. + if (_generate_distance_field) { + _scale_factor = 1.0; + } else if (_no_native_aa) { + _scale_factor = 4.0; + } else { + _scale_factor = 2.0; + } + } + + _text_maker->set_point_size(_point_size); + _text_maker->set_native_antialias(!_no_native_aa); + _text_maker->set_interior_flag(_got_interior); + _text_maker->set_pixels_per_unit(_pixels_per_unit); + _text_maker->set_scale_factor(_scale_factor); + + // The text_maker may have had to adjust the pixels per unit and the scale + // factor according to what the font supports. + _pixels_per_unit = _text_maker->get_pixels_per_unit(); + _scale_factor = _text_maker->get_scale_factor(); + + if (_text_maker->get_font_pixel_size() != 0) { + nout << "Using " << _text_maker->get_font_pixel_size() << "-pixel font.\n"; + } + + // Now we may want to tweak the scale factor so that fonts will actually be + // generated big. We have to do this after we have already send the current + // _scale_factor through the _text_maker for validation. + _palettize_scale_factor = _scale_factor; + if (_scale_factor != 1.0 && (_no_reduce || !_no_palettize)) { + // If _no_reduce is true (-nr was specified), we want to keep the glyph + // textures full-sized, because the user asked for that. + + // If _no_palettize is false (-nopal was not specified), we still want to + // keep the glyph textures full-sized, because the palettizer will reduce + // them later. + + _tex_margin = (int)(_tex_margin * _scale_factor); + _poly_margin *= _scale_factor; + _pixels_per_unit *= _scale_factor; + _scale_factor = 1.0; + _text_maker->set_pixels_per_unit(_pixels_per_unit); + _text_maker->set_scale_factor(1.0); + } + + if (_no_reduce) { + // If -nr was specified, but we're still palettizing, we don't even want + // to reduce the palette images. Instead, we'll generate extra-large + // palette images. + _palette_size[0] = (int)(_palette_size[0] * _palettize_scale_factor); + _palette_size[1] = (int)(_palette_size[1] * _palettize_scale_factor); + _palettize_scale_factor = 1.0; + } + + if (_range.is_empty()) { + // If there's no specified range, the default is the entire ASCII set. + _range.add_range(0x20, 0x7e); + + _range.add_singleton(0xa1); // Upside down exclamation mark + _range.add_singleton(0xa9); // Copyright sign + _range.add_singleton(0xab); // Left double angle quote + // _range.add_singleton(0xae); Registered sign + _range.add_singleton(0xb0); // Degree symbol + _range.add_singleton(0xb5); // Mu/micro + _range.add_singleton(0xb8); // Cedilla + _range.add_singleton(0xbb); // Right double angle quote + _range.add_singleton(0xbf); // Upside down question mark + + _range.add_singleton(0xc6); // AE ligature + _range.add_singleton(0xc7); // C cedilla + // _range.add_singleton(0xd0); Upper-case Eth _range.add_singleton(0xd8); + // Upper-case O with line _range.add_singleton(0xde); Upper-case Thorn + _range.add_singleton(0xdf); // German Eszet + _range.add_singleton(0xe6); // ae ligature + _range.add_singleton(0xe7); // c cedilla + _range.add_singleton(0xf0); // Lower-case Eth + _range.add_singleton(0xf8); // Lower-case O with line + _range.add_singleton(0xfe); // Lower-case Thorn + + // _range.add_singleton(0x03c0); pi + + // Dotless i and j, for combining purposes. + _range.add_singleton(0x0131); + _range.add_singleton(0x0237); + + // And general punctuation. These don't take up much space anyway. + _range.add_range(0x2018, 0x201f); + + _range.add_singleton(0x2026); // Ellipses + + // Also add all the combining diacritic marks. + _range.add_range(0x0300, 0x030f); + } + if (_output_glyph_pattern.empty()) { + // Create a default texture filename pattern. + _output_glyph_pattern = get_output_filename().get_fullpath_wo_extension() + "%03d.png"; + } + if (_output_palette_pattern.empty()) { + // Create a default texture filename pattern. + _output_palette_pattern = get_output_filename().get_fullpath_wo_extension() + "_%i"; + } + + // Figure out how many channels we need based on the foreground and + // background colors. + bool needs_alpha = (_fg[3] != 1.0 || _bg[3] != 1.0 || _interior[3] != 1.0); + bool needs_color = (_fg[0] != _fg[1] || _fg[1] != _fg[2] || + _bg[0] != _bg[1] || _bg[1] != _bg[2] || + _interior[0] != _interior[1] || _interior[1] != _interior[2]); + + if (needs_alpha) { + if (needs_color) { + _num_channels = 4; + _format = EggTexture::F_rgba; + } else { + if (_fg[0] == 1.0 && _bg[0] == 1.0 && _interior[0] == 1.0) { + // A special case: we only need an alpha channel. Copy the alpha data + // into the color channels so we can write out a one-channel image. + _fg[0] = _fg[1] = _fg[2] = _fg[3]; + _bg[0] = _bg[1] = _bg[2] = _bg[3]; + _interior[0] = _interior[1] = _interior[2] = _interior[3]; + _num_channels = 1; + _format = EggTexture::F_alpha; + } else { + _num_channels = 2; + _format = EggTexture::F_luminance_alpha; + } + } + } else { + if (needs_color) { + _num_channels = 3; + _format = EggTexture::F_rgb; + } else { + _num_channels = 1; + _format = EggTexture::F_luminance; + } + } + + // Create a global Palettizer object. We'll use this even if the user + // specified -nopal, if nothing else just to hold all of the TextureImage + // pointers. + pal = new Palettizer; + pal->_generated_image_pattern = _output_palette_pattern; + pal->_omit_solitary = false; + pal->_round_uvs = false; + + // Generate a txa script for the palettizer. We have the palettizer reduce + // all of the texture images by the inverse of our scale factor. + char buffer[1024]; + sprintf(buffer, ":margin 0;:coverage 1000;:background %f %f %f %f;:palette %d %d;*: %f%% keep-format", + _bg[0], _bg[1], _bg[2], _bg[3], + _palette_size[0], _palette_size[1], + 100.0 / _palettize_scale_factor); + std::istringstream txa_script(buffer); + pal->read_txa_file(txa_script, "default script"); + + pal->all_params_set(); + + // Now create all the egg structures. We can't use _data, since we want to + // pass this object to the palettizer, which will try to up its reference + // count. + PT(EggData) egg_data = new EggData; + _group = new EggGroup(); + egg_data->add_child(_group); + append_command_comment(egg_data); + + _vpool = new EggVertexPool("vpool"); + _group->add_child(_vpool); + + // Make the group a sequence, as a convenience. If we view the egg file + // directly we can see all the characters one at a time. + _group->set_switch_flag(true); + _group->set_switch_fps(2.0); + + double margin = _poly_margin; + if (_generate_distance_field) { + // Distance fields are always rendered with binary alpha. + _group->set_alpha_mode(EggRenderMode::AM_binary); + + // Fudged to make most fonts fit on 512x256. + if (_poly_margin >= 1) { + margin += 3.5; + _poly_margin -= 0.5; + } + + _text_maker->set_distance_field_radius(4); + } + + // Also create an egg group indicating the font's design size and poly + // margin. + EggGroup *ds_group = new EggGroup("ds"); + _group->add_child(ds_group); + EggVertex *vtx = make_vertex(LPoint2d(margin / _pixels_per_unit, _text_maker->get_line_height())); + EggPoint *point = new EggPoint; + ds_group->add_child(point); + point->add_vertex(vtx); + + // Finally, add the characters, one at a time. + RangeIterator ri(_range); + do { + add_character(ri.get_code()); + } while (ri.next()); + + // If there are extra glyphs, pick them up. + if (!_extra_filenames.empty()) { + vector_string::const_iterator si; + for (si = _extra_filenames.begin(); si != _extra_filenames.end(); ++si) { + add_extra_glyphs(*si); + } + } + + if (_no_palettize) { + // Ok, no palettize step; just write out the egg file and all of the + // textures. + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti); + texture->write(texture->read_source_image()); + } + + egg_data->write_egg(get_output()); + + } else { + // Pass the generated egg structure through egg-palettize, without writing + // it to disk first. + string name = get_output_filename().get_basename(); + EggFile *egg_file = pal->get_egg_file(name); + egg_file->from_command_line(egg_data, "", get_output_filename(), + get_exec_command()); + + pal->add_command_line_egg(egg_file); + pal->process_all(true, ""); + pal->optimal_resize(); + pal->generate_images(true); + if (!pal->write_eggs()) { + exit(1); + } + // pal->report_pi(); + } +} + +/** + * + */ +bool EggMakeFont:: +dispatch_range(const string &, const string &arg, void *var) { + RangeDescription *ip = (RangeDescription *)var; + return ip->parse_parameter(arg); +} + +/** + * Allocates and returns a new vertex from the vertex pool representing the + * indicated 2-d coordinates. + */ +EggVertex *EggMakeFont:: +make_vertex(const LPoint2d &xy) { + return + _vpool->make_new_vertex(LPoint3d::origin(_coordinate_system) + + LVector3d::rfu(xy[0], 0.0, xy[1], _coordinate_system)); +} + +/** + * Generates the indicated character and adds it to the font description. + */ +void EggMakeFont:: +add_character(int code) { + PNMTextGlyph *glyph = _text_maker->get_glyph(code); + if (glyph == nullptr) { + nout << "No definition in font for character " << code << ".\n"; + return; + } + + make_geom(glyph, code); +} + + +/** + * Creates the actual geometry for the glyph. + */ +void EggMakeFont:: +make_geom(PNMTextGlyph *glyph, int character) { + // Create an egg group to hold the polygon. + string group_name = format_string(character); + EggGroup *group = new EggGroup(group_name); + _group->add_child(group); + + if (glyph->get_width() != 0 && glyph->get_height() != 0) { + int bitmap_top = glyph->get_top(); + int bitmap_left = glyph->get_left(); + double tex_x_size = glyph->get_width(); + double tex_y_size = glyph->get_height(); + + double poly_margin = _poly_margin; + double x_origin = _tex_margin; + double y_origin = _tex_margin; + double page_y_size = tex_y_size + _tex_margin * 2; + double page_x_size = tex_x_size + _tex_margin * 2; + + // Determine the corners of the rectangle in geometric units. + double tex_poly_margin = poly_margin / _pixels_per_unit; + double origin_y = bitmap_top / _pixels_per_unit; + double origin_x = bitmap_left / _pixels_per_unit; + double top = origin_y + tex_poly_margin; + double left = origin_x - tex_poly_margin; + double bottom = origin_y - tex_y_size / _pixels_per_unit - tex_poly_margin; + double right = origin_x + tex_x_size / _pixels_per_unit + tex_poly_margin; + + // And the corresponding corners in UV units. + double uv_top = 1.0f - (double)(y_origin - poly_margin) / page_y_size; + double uv_left = (double)(x_origin - poly_margin) / page_x_size; + double uv_bottom = 1.0f - (double)(y_origin + poly_margin + tex_y_size) / page_y_size; + double uv_right = (double)(x_origin + poly_margin + tex_x_size) / page_x_size; + + // Create the vertices for the polygon. + EggVertex *v1 = make_vertex(LPoint2d(left, bottom)); + EggVertex *v2 = make_vertex(LPoint2d(right, bottom)); + EggVertex *v3 = make_vertex(LPoint2d(right, top)); + EggVertex *v4 = make_vertex(LPoint2d(left, top)); + + v1->set_uv(LTexCoordd(uv_left, uv_bottom)); + v2->set_uv(LTexCoordd(uv_right, uv_bottom)); + v3->set_uv(LTexCoordd(uv_right, uv_top)); + v4->set_uv(LTexCoordd(uv_left, uv_top)); + + EggPolygon *poly = new EggPolygon(); + group->add_child(poly); + poly->set_texture(get_tref(glyph, character)); + + poly->add_vertex(v1); + poly->add_vertex(v2); + poly->add_vertex(v3); + poly->add_vertex(v4); + } + + // Now create a single point where the origin of the next character will be. + + EggVertex *v0 = make_vertex(LPoint2d(glyph->get_advance() / _pixels_per_unit + _render_margin, 0.0)); + EggPoint *point = new EggPoint; + group->add_child(point); + point->add_vertex(v0); +} + +/** + * Returns the egg texture reference for a particular glyph, creating it if it + * has not already been created. + */ +EggTexture *EggMakeFont:: +get_tref(PNMTextGlyph *glyph, int character) { + TRefs::iterator ti = _trefs.find(glyph); + if (ti != _trefs.end()) { + return (*ti).second; + } + + EggTexture *tref = make_tref(glyph, character); + _trefs[glyph] = tref; + return tref; +} + +/** + * Generates a texture image for the indicated glyph, and returns its egg + * reference. + */ +EggTexture *EggMakeFont:: +make_tref(PNMTextGlyph *glyph, int character) { + char buffer[1024]; + sprintf(buffer, _output_glyph_pattern.c_str(), character); + + Filename texture_filename = buffer; + PNMImage image(glyph->get_width() + _tex_margin * 2, + glyph->get_height() + _tex_margin * 2, _num_channels); + image.fill(_bg[0], _bg[1], _bg[2]); + if (image.has_alpha()) { + image.alpha_fill(_bg[3]); + } + if (_got_interior) { + glyph->place(image, -glyph->get_left() + _tex_margin, + glyph->get_top() + _tex_margin, _fg, _interior); + } else { + glyph->place(image, -glyph->get_left() + _tex_margin, + glyph->get_top() + _tex_margin, _fg); + } + + // We don't write the image to disk immediately, since it might just get + // palettized. But we do record it in a TextureImage object within the + // global Palettizer, so that it may be written out later. + + string name = texture_filename.get_basename_wo_extension(); + TextureImage *texture = pal->get_texture(name); + _textures.push_back(texture); + texture->set_filename("", texture_filename); + SourceTextureImage *source = texture->get_source(texture_filename, "", 0); + texture->set_source_image(image); + source->set_header(image); + + EggTexture *tref = new EggTexture(name, texture_filename); + tref->set_format(_format); + tref->set_wrap_mode(EggTexture::WM_clamp); + tref->set_minfilter(EggTexture::FT_linear_mipmap_linear); + tref->set_magfilter(EggTexture::FT_linear); + tref->set_quality_level(EggTexture::QL_best); + + return tref; +} + +/** + * Reads the indicated filename and adds any numbered groups into the current + * egg file. + */ +void EggMakeFont:: +add_extra_glyphs(const Filename &extra_filename) { + PT(EggData) extra_data = new EggData; + + if (!extra_data->read(extra_filename)) { + return; + } + + _group->steal_children(*extra_data); +} + +/** + * Recursively searches for numbered groups in the indicated egg file, and + * copies them to the current egg file. + */ +void EggMakeFont:: +r_add_extra_glyphs(EggGroupNode *egg_group) { + if (egg_group->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, egg_group); + if (is_numeric(group->get_name())) { + EggGroup *new_group = new EggGroup(group->get_name()); + _group->add_child(new_group); + new_group->steal_children(*group); + return; + } + } + + EggGroupNode::iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + EggNode *child = (*ci); + if (child->is_of_type(EggGroupNode::get_class_type())) { + r_add_extra_glyphs(DCAST(EggGroupNode, child)); + } + } +} + +/** + * Returns true if the indicated string is all numeric digits, false + * otherwise. + */ +bool EggMakeFont:: +is_numeric(const string &str) { + if (str.empty()) { + return false; + } + + string::const_iterator si; + for (si = str.begin(); si != str.end(); ++si) { + if (!isdigit(*si)) { + return false; + } + } + + return true; +} + +int main(int argc, char *argv[]) { + EggMakeFont prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/egg-mkfont/eggMakeFont.h b/pandatool/src/egg-mkfont/eggMakeFont.h new file mode 100644 index 00000000..3df12af6 --- /dev/null +++ b/pandatool/src/egg-mkfont/eggMakeFont.h @@ -0,0 +1,100 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeFont.h + * @author drose + * @date 2001-02-16 + */ + +#ifndef EGGMAKEFONT_H +#define EGGMAKEFONT_H + +#include "pandatoolbase.h" +#include "rangeDescription.h" + +#include "eggWriter.h" +#include "eggTexture.h" +#include "pmap.h" +#include "pvector.h" +#include "vector_string.h" + +class PNMTextMaker; +class PNMTextGlyph; +class EggVertexPool; +class EggGroup; +class TextureImage; + +/** + * This program uses FreeType to generate an egg file and a series of texture + * images from a font file input, such as a TTF file. The resulting egg file + * can be loaded in Panda as a StaticTextFont object for rendering text, even + * if FreeType is not compiled into the executing Panda. + */ +class EggMakeFont : public EggWriter { +public: + EggMakeFont(); + +protected: + virtual bool handle_args(Args &args); + +public: + void run(); + +private: + static bool dispatch_range(const std::string &, const std::string &arg, void *var); + EggVertex *make_vertex(const LPoint2d &xy); + + void add_character(int code); + void make_geom(PNMTextGlyph *glyph, int character); + EggTexture *get_tref(PNMTextGlyph *glyph, int character); + EggTexture *make_tref(PNMTextGlyph *glyph, int character); + void add_extra_glyphs(const Filename &extra_filename); + void r_add_extra_glyphs(EggGroupNode *egg_group); + static bool is_numeric(const std::string &str); + + +private: + LColor _fg, _bg, _interior; + bool _got_interior; + RangeDescription _range; + vector_string _extra_filenames; + double _pixels_per_unit; + double _point_size; + double _poly_margin; + int _tex_margin; + double _render_margin; + bool _got_scale_factor; + double _scale_factor; + bool _no_reduce; + bool _no_native_aa; + bool _no_palettize; + int _palette_size[2]; + bool _generate_distance_field; + + double _palettize_scale_factor; + Filename _input_font_filename; + int _face_index; + std::string _output_glyph_pattern; + std::string _output_palette_pattern; + + PNMTextMaker *_text_maker; + + EggTexture::Format _format; + int _num_channels; + EggVertexPool *_vpool; + EggGroup *_group; + + typedef pmap TRefs; + TRefs _trefs; + + typedef pvector Textures; + Textures _textures; +}; + + +#endif diff --git a/pandatool/src/egg-mkfont/rangeDescription.I b/pandatool/src/egg-mkfont/rangeDescription.I new file mode 100644 index 00000000..39a20162 --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeDescription.I @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeDescription.I + * @author drose + * @date 2003-09-07 + */ + +/** + * + */ +INLINE void RangeDescription:: +add_singleton(int code) { + _range_list.push_back(Range(code)); +} + +/** + * + */ +INLINE void RangeDescription:: +add_range(int from_code, int to_code) { + _range_list.push_back(Range(from_code, to_code)); +} + +/** + * Returns true if there are no codes described in the range. + */ +INLINE bool RangeDescription:: +is_empty() const { + return _range_list.empty(); +} + +/** + * + */ +INLINE RangeDescription::Range:: +Range(int code) : + _from_code(code), + _to_code(code) +{ +} + +/** + * + */ +INLINE RangeDescription::Range:: +Range(int from_code, int to_code) : + _from_code(from_code), + _to_code(to_code) +{ +} + +INLINE std::ostream &operator << (std::ostream &out, const RangeDescription &range) { + range.output(out); + return out; +} diff --git a/pandatool/src/egg-mkfont/rangeDescription.cxx b/pandatool/src/egg-mkfont/rangeDescription.cxx new file mode 100644 index 00000000..2833f533 --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeDescription.cxx @@ -0,0 +1,175 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeDescription.cxx + * @author drose + * @date 2003-09-07 + */ + +#include "rangeDescription.h" +#include "string_utils.h" +#include "pnotify.h" + +using std::string; + +/** + * + */ +RangeDescription:: +RangeDescription() { +} + +/** + * Parses a string of comma- and hyphen-delimited unicode values, in decimal + * and/or hex, including possible bracket-delimited ASCII characters, as may + * have been passed on a command line. Returns true if the parameter is + * parsed correctly, false otherwise. + */ +bool RangeDescription:: +parse_parameter(const string ¶m) { + // First, go through and separate the string by commas. We have to do this + // by hand instead of calling tokenize(), because we also have to scan for + // square brackets, which may contain nested commas. + size_t p = 0; + while (p < param.length()) { + size_t q = param.find_first_of("[,", p); + if (q == string::npos) { + return parse_word(trim(param.substr(p))); + } + if (!parse_word(trim(param.substr(p, q - p)))) { + return false; + } + + if (param[q] == '[') { + // A square bracket means we must search for the matching square + // bracket. However, a right bracket immediately after the left bracket + // doesn't count; we start the scan after that. + p = param.find("]", q + 2); + if ( p == string::npos) { + nout << "Unclosed open bracket.\n"; + return false; + } + if (!parse_bracket(param.substr(q + 1, p - q - 1))) { + return false; + } + p = p + 1; + + } else { + // Otherwise, if the separator was just a comma, the next character + // begins the next word. + p = q + 1; + } + } + + return true; +} + +/** + * + */ +void RangeDescription:: +output(std::ostream &out) const { + bool first_time = true; + RangeList::const_iterator ri; + for (ri = _range_list.begin(); ri != _range_list.end(); ++ri) { + const Range &range = (*ri); + if (!first_time) { + out << ","; + } + first_time = false; + if (range._from_code == range._to_code) { + out << range._from_code; + } else { + out << range._from_code << "-" << range._to_code; + } + } +} + +/** + * Parses a single "word", i.e. the text delimited by commas, that might be + * listed on the command line. This is generally either the empty string, a + * single number, or a pair of numbers separated by a hyphen. + */ +bool RangeDescription:: +parse_word(const string &word) { + if (word.empty()) { + return true; + } + + // It's not empty, so see if it includes a hyphen. + size_t hyphen = word.find('-'); + if (hyphen == string::npos) { + // Nope, just one number. + int code; + if (!parse_code(word, code)) { + return false; + } + add_singleton(code); + + } else { + // Two numbers separated by a hyphen. + int from_code, to_code; + if (!parse_code(word.substr(0, hyphen), from_code)) { + return false; + } + if (!parse_code(word.substr(hyphen + 1), to_code)) { + return false; + } + add_range(from_code, to_code); + } + + return true; +} + +/** + * Parses a single numeric value, either decimal or hexadecimal, and stores it + * in the indicated parameter. Returns true if successful, false otherwise. + */ +bool RangeDescription:: +parse_code(const string &word, int &code) { + string str = trim(word); + const char *nptr = str.c_str(); + char *endptr; + code = strtol(nptr, &endptr, 0); + if (*endptr == '\0') { + return true; + } + + nout << "Invalid Unicode value: " << word << "\n"; + return false; +} + +/** + * Parses the text listed between square brackets on the command line. + */ +bool RangeDescription:: +parse_bracket(const string &str) { + string::const_iterator si; + si = str.begin(); + while (si != str.end()) { + int ch = (*si); + ++si; + if (si != str.end() && (*si) == '-') { + // A hyphen indicates a range. + ++si; + if (si == str.end()) { + // Unless the hyphen is the last character. + add_singleton(ch); + add_singleton('-'); + } else { + add_range(ch, (*si)); + ++si; + } + } else { + // Anything other than a hyphen indicates a singleton. + add_singleton(ch); + } + } + + return true; +} diff --git a/pandatool/src/egg-mkfont/rangeDescription.h b/pandatool/src/egg-mkfont/rangeDescription.h new file mode 100644 index 00000000..25119cb1 --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeDescription.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeDescription.h + * @author drose + * @date 2003-09-07 + */ + +#ifndef RANGEDESCRIPTION_H +#define RANGEDESCRIPTION_H + +#include "pandatoolbase.h" +#include "pvector.h" + +/** + * This describes a sparse range of Unicode character codes for conversion + * that may be specified on the command line. + */ +class RangeDescription { +public: + RangeDescription(); + + bool parse_parameter(const std::string ¶m); + INLINE void add_singleton(int code); + INLINE void add_range(int from_code, int to_code); + INLINE bool is_empty() const; + + void output(std::ostream &out) const; + +private: + bool parse_word(const std::string &word); + bool parse_code(const std::string &word, int &code); + bool parse_bracket(const std::string &str); + +private: + class Range { + public: + INLINE Range(int code); + INLINE Range(int from_code, int to_code); + + int _from_code; + int _to_code; + }; + + typedef pvector RangeList; + RangeList _range_list; + + friend class RangeIterator; +}; + +INLINE std::ostream &operator << (std::ostream &out, const RangeDescription &range); + +#include "rangeDescription.I" + +#endif diff --git a/pandatool/src/egg-mkfont/rangeIterator.I b/pandatool/src/egg-mkfont/rangeIterator.I new file mode 100644 index 00000000..5d34d4ec --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeIterator.I @@ -0,0 +1,29 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeIterator.I + * @author drose + * @date 2003-09-07 + */ + +/** + * Returns the current Unicode value represented by the iterator, or -1 if the + * iterator has reached the end. + */ +INLINE int RangeIterator:: +get_code() const { + return _code; +} + +/** + * Returns true if all the code have been retrieved, false otherwise. + */ +INLINE bool RangeIterator:: +eof() const { + return (_it == _desc._range_list.end()); +} diff --git a/pandatool/src/egg-mkfont/rangeIterator.cxx b/pandatool/src/egg-mkfont/rangeIterator.cxx new file mode 100644 index 00000000..0050c80b --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeIterator.cxx @@ -0,0 +1,63 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeIterator.cxx + * @author drose + * @date 2003-09-07 + */ + +#include "rangeIterator.h" + +/** + * Constructs an iterator to walk through the codes on the descriptor. It is + * important not to modify the RangeDescription object during the lifetime of + * the iterator. + */ +RangeIterator:: +RangeIterator(const RangeDescription &desc) : + _desc(desc) +{ + _it = _desc._range_list.begin(); + if (_it == _desc._range_list.end()) { + _code = -1; + } else { + _code = (*_it)._from_code; + _codes_generated.insert(_code); + } +} + +/** + * Advances the iterator to the next code. Returns true if there is a next + * code, or false if there are no mode codes. + */ +bool RangeIterator:: +next() { + do { + if (_it == _desc._range_list.end()) { + return false; + } + + if (_code < (*_it)._to_code) { + _code++; + + } else { + _it++; + if (_it == _desc._range_list.end()) { + _code = -1; + return false; + } + + _code = (*_it)._from_code; + } + + // If this code has already been generated, repeat and skip to the next + // one. + } while (!_codes_generated.insert(_code).second); + + return true; +} diff --git a/pandatool/src/egg-mkfont/rangeIterator.h b/pandatool/src/egg-mkfont/rangeIterator.h new file mode 100644 index 00000000..5a9630d7 --- /dev/null +++ b/pandatool/src/egg-mkfont/rangeIterator.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file rangeIterator.h + * @author drose + * @date 2003-09-07 + */ + +#ifndef RANGEITERATOR_H +#define RANGEITERATOR_H + +#include "pandatoolbase.h" +#include "rangeDescription.h" + +#include "pset.h" + +/** + * Walks through all the Unicode characters described by a RangeDescription + * class. + */ +class RangeIterator { +public: + RangeIterator(const RangeDescription &desc); + + INLINE int get_code() const; + bool next(); + INLINE bool eof() const; + +private: + const RangeDescription &_desc; + RangeDescription::RangeList::const_iterator _it; + int _code; + + typedef pset Codes; + Codes _codes_generated; +}; + +#include "rangeIterator.I" + +#endif diff --git a/pandatool/src/egg-optchar/CMakeLists.txt b/pandatool/src/egg-optchar/CMakeLists.txt new file mode 100644 index 00000000..379fc301 --- /dev/null +++ b/pandatool/src/egg-optchar/CMakeLists.txt @@ -0,0 +1,27 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG) + return() +endif() + +set(P3EGG_OPTCHAR_HEADERS + config_egg_optchar.h + eggOptchar.h + eggOptcharUserData.h eggOptcharUserData.I + vertexMembership.h vertexMembership.I +) + +set(P3EGG_OPTCHAR_SOURCES + config_egg_optchar.cxx + eggOptchar.cxx + eggOptcharUserData.cxx + vertexMembership.cxx +) + +composite_sources(egg-optchar P3EGG_OPTCHAR_SOURCES) +add_executable(egg-optchar ${P3EGG_OPTCHAR_HEADERS} ${P3EGG_OPTCHAR_SOURCES}) +target_link_libraries(egg-optchar p3eggcharbase) + +install(TARGETS egg-optchar EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/egg-optchar/config_egg_optchar.cxx b/pandatool/src/egg-optchar/config_egg_optchar.cxx new file mode 100644 index 00000000..ef835f1f --- /dev/null +++ b/pandatool/src/egg-optchar/config_egg_optchar.cxx @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_egg_optchar.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "config_egg_optchar.h" +#include "eggOptcharUserData.h" + +#include "dconfig.h" + + +Configure(config_egg_optchar); + +ConfigureFn(config_egg_optchar) { + init_egg_optchar(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_egg_optchar() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + EggOptcharUserData::init_type(); +} diff --git a/pandatool/src/egg-optchar/config_egg_optchar.h b/pandatool/src/egg-optchar/config_egg_optchar.h new file mode 100644 index 00000000..9d01b7fe --- /dev/null +++ b/pandatool/src/egg-optchar/config_egg_optchar.h @@ -0,0 +1,21 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_egg_optchar.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef CONFIG_EGG_OPTCHAR_H +#define CONFIG_EGG_OPTCHAR_H + +#include "pandatoolbase.h" + +void init_egg_optchar(); + +#endif /* CONFIG_EGG_OPTCHAR_H */ diff --git a/pandatool/src/egg-optchar/eggOptchar.cxx b/pandatool/src/egg-optchar/eggOptchar.cxx new file mode 100644 index 00000000..182b6579 --- /dev/null +++ b/pandatool/src/egg-optchar/eggOptchar.cxx @@ -0,0 +1,1522 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggOptchar.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "eggOptchar.h" +#include "eggOptcharUserData.h" +#include "vertexMembership.h" + +#include "eggJointData.h" +#include "eggSliderData.h" +#include "eggCharacterCollection.h" +#include "eggCharacterData.h" +#include "eggBackPointer.h" +#include "eggGroupNode.h" +#include "eggPrimitive.h" +#include "eggVertexPool.h" +#include "eggTable.h" +#include "eggGroup.h" +#include "eggAnimPreload.h" +#include "string_utils.h" +#include "dcast.h" +#include "pset.h" +#include "compose_matrix.h" +#include "fftCompressor.h" + +#include + +using std::cout; +using std::setw; +using std::string; + +/** + * + */ +EggOptchar:: +EggOptchar() { + add_path_replace_options(); + add_path_store_options(); + add_normals_options(); + add_transform_options(); + add_fixrest_option(); + + set_program_brief("optimizes character models and animations in .egg files"); + set_program_description + ("egg-optchar performs basic optimizations of a character model " + "and its associated animations, primarily by analyzing the " + "animation tables and removing unneeded joints and/or morphs. " + "It can also perform basic restructuring operations on the " + "character hierarchy."); + + add_option + ("ls", "", 0, + "List the joint hierarchy instead of performing any operations.", + &EggOptchar::dispatch_none, &_list_hierarchy); + + add_option + ("lsv", "", 0, + "List the joint hierarchy along with an indication of the properties " + "each joint.", + &EggOptchar::dispatch_none, &_list_hierarchy_v); + + add_option + ("lsp", "", 0, + "List the existing joint hierarchy as a series of -p joint,parent " + "commands, suitable for pasting into an egg-optchar command line.", + &EggOptchar::dispatch_none, &_list_hierarchy_p); + + add_option + ("keep", "joint[,joint...]", 0, + "Keep the named joints (or sliders) in the character, even if they do " + "not appear to be needed by the animation.", + &EggOptchar::dispatch_vector_string_comma, nullptr, &_keep_components); + + add_option + ("drop", "joint[,joint...]", 0, + "Removes the named joints or sliders, even if they appear to be needed.", + &EggOptchar::dispatch_vector_string_comma, nullptr, &_drop_components); + + add_option + ("expose", "joint[,joint...]", 0, + "Expose the named joints by flagging them with a DCS attribute, so " + "each one can be found in the scene graph when the character is loaded, " + "and objects can be parented to it. This implies -keep.", + &EggOptchar::dispatch_vector_string_comma, nullptr, &_expose_components); + + add_option + ("suppress", "joint[,joint...]", 0, + "The opposite of suppress, this prevents the named joints from " + "being created with an implicit DCS attribute, even if they contain " + "rigid geometry. The default is to create an implicit node for any " + "joint that contains rigid geometry, to take advantage of display " + "list and/or vertex buffer caching. This does not imply -keep.", + &EggOptchar::dispatch_vector_string_comma, nullptr, &_suppress_components); + + add_option + ("flag", "node[,node...][=name]", 0, + "Assign the indicated name to the geometry within the given nodes. " + "This will make the geometry visible as a node in the resulting " + "character model when it is loaded in the scene graph (normally, " + "the node hierarchy is suppressed when loading characters). This " + "is different from -expose in that it reveals geometry rather than " + "joints; the revealed node can be hidden or its attributes changed " + "at runtime, but it will be animated by its vertices, not the node, so " + "objects parented to this node will not inherit its animation.", + &EggOptchar::dispatch_flag_groups, nullptr, &_flag_groups); + + add_option + ("defpose", "anim.egg,frame", 0, + "Specify the model's default pose. The pose is taken " + "from the indicated frame of the named animation file (which must " + "also be named separately on the command line). The " + "pose will be held by the model in " + "the absence of any animation, and need not be the same " + "pose in which the model was originally skinned.", + &EggOptchar::dispatch_string, nullptr, &_defpose); + + add_option + ("preload", "", 0, + "Add an entry for each animation to the model file(s). " + "This can be used at runtime to support asynchronous " + "loading and binding of animation channels.", + &EggOptchar::dispatch_none, &_preload); + + add_option + ("zero", "joint[,hprxyzijkabc]", 0, + "Zeroes out the animation channels for the named joint. If " + "a subset of the component letters hprxyzijkabc is included, the " + "operation is restricted to just those components; otherwise the " + "entire transform is cleared.", + &EggOptchar::dispatch_name_components, nullptr, &_zero_channels); + + add_option + ("keepall", "", 0, + "Keep all joints and sliders in the character, except those named " + "explicitly by -drop.", + &EggOptchar::dispatch_none, &_keep_all); + + add_option + ("p", "joint,parent", 0, + "Moves the named joint under the named parent joint. Use " + "\"-p joint,\" to reparent a joint to the root. The joint transform " + "is recomputed appropriately under its new parent so that the animation " + "is not affected (the effect is similar to NodePath::wrt_reparent_to).", + &EggOptchar::dispatch_vector_string_pair, nullptr, &_reparent_joints); + + add_option + ("new", "joint,source", 0, + "Creates a new joint under the named parent joint. The new " + "joint will inherit the same net transform as its parent.", + &EggOptchar::dispatch_vector_string_pair, nullptr, &_new_joints); + + add_option + ("rename", "joint,newjoint", 0, + "Renames the indicated joint, if present, to the given name.", + &EggOptchar::dispatch_vector_string_pair, nullptr, &_rename_joints); + + if (FFTCompressor::is_compression_available()) { + add_option + ("optimal", "", 0, + "Computes the optimal joint hierarchy for the character by analyzing " + "all of the joint animation and reparenting joints to minimize " + "transformations. This can repair skeletons that have been flattened " + "or whose hierarchy was otherwise damaged in conversion; it can also " + "detect joints that are constrained to follow other joints and should " + "therefore be parented to the master joints. The result is a file " + "from which more joints may be successfully removed, that generally " + "compresses better and with fewer artifacts. However, this is a " + "fairly expensive operation.", + &EggOptchar::dispatch_none, &_optimal_hierarchy); + } + + add_option + ("q", "quantum", 0, + "Quantize joint membership values to the given unit. This is " + "the smallest significant change in joint membership. There can " + "be a significant performance (and memory utilization) runtime " + "benefit for eliminating small differences in joint memberships " + "between neighboring vertices. The default is 0.01; specifying " + "0 means to preserve the original values.", + &EggOptchar::dispatch_double, nullptr, &_vref_quantum); + + add_option + ("qa", "quantum[,hprxyzijkabc]", 0, + "Quantizes animation channels to the given unit. This rounds each " + "of the named components of all joints to the nearest multiple of unit. " + "There is no performance benefit, and little compression benefit, " + "for doing this; and this may introduce visible artifacts to the " + "animation. However, sometimes it is a useful tool for animation " + "analysis and comparison. This option may be repeated several times " + "to quantize different channels by a different amount.", + &EggOptchar::dispatch_double_components, nullptr, &_quantize_anims); + + add_option + ("dart", "[default, sync, nosync, or structured]", 0, + "change the dart value in the given eggs", + &EggOptchar::dispatch_string, nullptr, &_dart_type); + + + _optimal_hierarchy = false; + _vref_quantum = 0.01; +} + +/** + * + */ +void EggOptchar:: +run() { + // We have to apply the user-specified reparent requests first, before we + // even analyze the joints. This is because reparenting the joints may + // change their properties. + if (apply_user_reparents()) { + nout << "Reparenting hierarchy.\n"; + // So we'll have to call do_reparent() twice. It seems wasteful, but it + // really is necessary, and it's not that bad. + do_reparent(); + } + + if (!_zero_channels.empty()) { + zero_channels(); + } + + int num_characters = _collection->get_num_characters(); + int ci; + + // Now we can analyze the joints for their properties. + for (ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + analyze_joints(char_data->get_root_joint(), 0); + analyze_sliders(char_data); + } + + if (_list_hierarchy || _list_hierarchy_v) { + for (ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + nout << "Character: " << char_data->get_name() << "\n"; + list_joints(char_data->get_root_joint(), 0, _list_hierarchy_v); + list_scalars(char_data, _list_hierarchy_v); + nout << char_data->get_num_joints() << " joints.\n"; + } + + } else if (_list_hierarchy_p) { + for (ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + nout << "Character: " << char_data->get_name() << "\n"; + int col = 0; + list_joints_p(char_data->get_root_joint(), col); + // A newline to cout is needed after the above call. + cout << "\n"; + nout << char_data->get_num_joints() << " joints.\n"; + } + + } else { + // The meat of the program: determine which joints are to be removed, and + // then actually remove them. + determine_removed_components(); + move_vertices(); + if (process_joints()) { + do_reparent(); + } + + // We currently do not implement optimizing morph sliders. Need to add + // this at some point; it's quite easy. Identity and empty morph sliders + // can simply be removed, while static sliders need to be applied to the + // vertices and then removed. + + rename_joints(); + + // Quantize the vertex memberships. We call this even if _vref_quantum is + // 0, because this also normalizes the vertex memberships. + quantize_vertices(); + + // Also quantize the animation channels, if the user so requested. + quantize_channels(); + + // flag all the groups as the user requested. + if (!_flag_groups.empty()) { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + do_flag_groups(*ei); + } + } + + // Add the AnimPreload entries. + if (_preload) { + do_preload(); + } + + + // Finally, set the default poses. It's important not to do this until + // after we have adjusted all of the transforms for the various joints. + if (!_defpose.empty()) { + do_defpose(); + } + + if (!_dart_type.empty()) { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + change_dart_type(*ei, _dart_type); + } + } + + write_eggs(); + } +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggOptchar:: +handle_args(ProgramBase::Args &args) { + if (_list_hierarchy || _list_hierarchy_v || _list_hierarchy_p) { + _read_only = true; + } + + return EggCharacterFilter::handle_args(args); +} + +/** + * Standard dispatch function for an option that takes a pair of string + * parameters. The data pointer is to StringPairs vector; the pair will be + * pushed onto the end of the vector. + */ +bool EggOptchar:: +dispatch_vector_string_pair(const string &opt, const string &arg, void *var) { + StringPairs *ip = (StringPairs *)var; + + vector_string words; + tokenize(arg, words, ","); + + if (words.size() == 2) { + StringPair sp; + sp._a = words[0]; + sp._b = words[1]; + ip->push_back(sp); + + } else { + nout << "-" << opt + << " requires a pair of strings separated by a comma.\n"; + return false; + } + + return true; +} + +/** + * Accepts a name optionally followed by a comma and some of the nine standard + * component letters, + * + * The data pointer is to StringPairs vector; the pair will be pushed onto the + * end of the vector. + */ +bool EggOptchar:: +dispatch_name_components(const string &opt, const string &arg, void *var) { + StringPairs *ip = (StringPairs *)var; + + vector_string words; + tokenize(arg, words, ","); + + StringPair sp; + if (words.size() == 1) { + sp._a = words[0]; + + } else if (words.size() == 2) { + sp._a = words[0]; + sp._b = words[1]; + + } else { + nout << "-" << opt + << " requires a pair of strings separated by a comma.\n"; + return false; + } + + if (sp._b.empty()) { + sp._b = matrix_component_letters; + } else { + for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) { + if (strchr(matrix_component_letters, *si) == nullptr) { + nout << "Not a standard matrix component: \"" << *si << "\"\n" + << "-" << opt << " requires a joint name followed by a set " + << "of component names. The standard component names are \"" + << matrix_component_letters << "\".\n"; + return false; + } + } + } + + ip->push_back(sp); + + return true; +} + +/** + * Accepts a double value optionally followed by a comma and some of the nine + * standard component letters, + * + * The data pointer is to a DoubleStrings vector; the pair will be pushed onto + * the end of the vector. + */ +bool EggOptchar:: +dispatch_double_components(const string &opt, const string &arg, void *var) { + DoubleStrings *ip = (DoubleStrings *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool valid_double = false; + + DoubleString sp; + if (words.size() == 1) { + valid_double = string_to_double(words[0], sp._a); + + } else if (words.size() == 2) { + valid_double = string_to_double(words[0], sp._a); + sp._b = words[1]; + + } else { + nout << "-" << opt + << " requires a numeric value followed by a string.\n"; + return false; + } + + if (!valid_double) { + nout << "-" << opt + << " requires a numeric value followed by a string.\n"; + return false; + } + + if (sp._b.empty()) { + sp._b = matrix_component_letters; + } else { + for (string::const_iterator si = sp._b.begin(); si != sp._b.end(); ++si) { + if (strchr(matrix_component_letters, *si) == nullptr) { + nout << "Not a standard matrix component: \"" << *si << "\"\n" + << "-" << opt << " requires a joint name followed by a set " + << "of component names. The standard component names are \"" + << matrix_component_letters << "\".\n"; + return false; + } + } + } + + ip->push_back(sp); + + return true; +} + +/** + * Accepts a set of comma-delimited group names followed by an optional name + * separated with an equal sign. + * + * The data pointer is to a FlagGroups object. + */ +bool EggOptchar:: +dispatch_flag_groups(const string &opt, const string &arg, void *var) { + FlagGroups *ip = (FlagGroups *)var; + + vector_string words; + + tokenize(arg, words, ","); + + if (words.empty()) { + nout << "-" << opt + << " requires a series of words separated by a comma.\n"; + return false; + } + + FlagGroupsEntry entry; + + // Check for an equal sign in the last word. This marks the name to assign. + string &last_word = words.back(); + size_t equals = last_word.rfind('='); + if (equals != string::npos) { + entry._name = last_word.substr(equals + 1); + last_word = last_word.substr(0, equals); + + } else { + // If there's no equal sign, the default is to name all groups after the + // group itself. We leave the name empty to indicate that. + } + + // Convert the words to GlobPatterns. + vector_string::const_iterator si; + for (si = words.begin(); si != words.end(); ++si) { + const string &word = (*si); + entry._groups.push_back(GlobPattern(word)); + } + + ip->push_back(entry); + + return true; +} + +/** + * Flag all joints and sliders that should be removed for optimization + * purposes. + */ +void EggOptchar:: +determine_removed_components() { + typedef pset Names; + Names keep_names; + Names drop_names; + Names expose_names; + Names suppress_names; + Names names_used; + + vector_string::const_iterator si; + for (si = _keep_components.begin(); si != _keep_components.end(); ++si) { + keep_names.insert(*si); + } + for (si = _drop_components.begin(); si != _drop_components.end(); ++si) { + drop_names.insert(*si); + } + for (si = _expose_components.begin(); si != _expose_components.end(); ++si) { + keep_names.insert(*si); + expose_names.insert(*si); + } + for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) { + suppress_names.insert(*si); + } + + // We always keep the root joint, which has no name. + keep_names.insert(""); + + int num_characters = _collection->get_num_characters(); + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + int num_components = char_data->get_num_components(); + nout << char_data->get_name() << " has " << num_components << " components.\n"; + for (int i = 0; i < num_components; i++) { + EggComponentData *comp_data = char_data->get_component(i); + nassertv(comp_data != nullptr); + + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, comp_data->get_user_data()); + nassertv(user_data != nullptr); + + const string &name = comp_data->get_name(); + if (suppress_names.find(name) != suppress_names.end()) { + // If this component is not dropped, it will not be implicitly + // exposed. + names_used.insert(name); + user_data->_flags |= EggOptcharUserData::F_suppress; + } + + if (drop_names.find(name) != drop_names.end()) { + // Remove this component by user request. + names_used.insert(name); + user_data->_flags |= EggOptcharUserData::F_remove; + + } else if (_keep_all || keep_names.find(name) != keep_names.end()) { + // Keep this component. + names_used.insert(name); + + if (expose_names.find(name) != expose_names.end()) { + // In fact, expose it. + user_data->_flags |= EggOptcharUserData::F_expose; + } + + } else { + // Remove this component if it's unanimated or empty. + if ((user_data->_flags & (EggOptcharUserData::F_static | EggOptcharUserData::F_empty)) != 0) { + if ((user_data->_flags & (EggOptcharUserData::F_top | EggOptcharUserData::F_empty)) == EggOptcharUserData::F_top) { + // Actually, we can't remove it if it's a top joint, unless it's + // also empty. That's because vertices that are partially + // assigned to this joint would then have no joint to represent + // the same partial assignment, and they would then appear to be + // wholly assigned to their other joint, which would be incorrect. + + } else { + // But joints that aren't top joints (or that are empty) are o.k. + // to remove. + user_data->_flags |= EggOptcharUserData::F_remove; + } + } + } + } + } + + // Go back and tell the user about component names we didn't use, just to be + // helpful. + for (si = _keep_components.begin(); si != _keep_components.end(); ++si) { + const string &name = (*si); + if (names_used.find(name) == names_used.end()) { + nout << "No such component: " << name << "\n"; + } + } + for (si = _drop_components.begin(); si != _drop_components.end(); ++si) { + const string &name = (*si); + if (names_used.find(name) == names_used.end()) { + nout << "No such component: " << name << "\n"; + } + } + for (si = _expose_components.begin(); si != _expose_components.end(); ++si) { + const string &name = (*si); + if (names_used.find(name) == names_used.end()) { + nout << "No such component: " << name << "\n"; + } + } + for (si = _suppress_components.begin(); si != _suppress_components.end(); ++si) { + const string &name = (*si); + if (names_used.find(name) == names_used.end()) { + nout << "No such component: " << name << "\n"; + } + } +} + +/** + * Moves the vertices from joints that are about to be removed into the first + * suitable parent. This might result in fewer joints being removed (because + * the parent might suddenly no longer be empty). + */ +void EggOptchar:: +move_vertices() { + int num_characters = _collection->get_num_characters(); + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + int num_joints = char_data->get_num_joints(); + + for (int i = 0; i < num_joints; i++) { + EggJointData *joint_data = char_data->get_joint(i); + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, joint_data->get_user_data()); + + if ((user_data->_flags & EggOptcharUserData::F_empty) == 0 && + (user_data->_flags & EggOptcharUserData::F_remove) != 0) { + // This joint has vertices, but is scheduled to be removed; find a + // suitable home for its vertices. + EggJointData *best_joint = find_best_vertex_joint(joint_data->get_parent()); + joint_data->move_vertices_to(best_joint); + + // Now we can't remove the joint. + if (best_joint != nullptr) { + EggOptcharUserData *best_user_data = + DCAST(EggOptcharUserData, best_joint->get_user_data()); + best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove); + } + } + } + } +} + + +/** + * Effects the actual removal of joints flagged for removal by reparenting the + * hierarchy appropriately. Returns true if any joints are removed, false + * otherwise. + */ +bool EggOptchar:: +process_joints() { + bool removed_any = false; + int num_characters = _collection->get_num_characters(); + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + int num_joints = char_data->get_num_joints(); + + int num_static = 0; + int num_empty = 0; + int num_identity = 0; + int num_other = 0; + int num_kept = 0; + + for (int i = 0; i < num_joints; i++) { + EggJointData *joint_data = char_data->get_joint(i); + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, joint_data->get_user_data()); + + if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) { + // This joint will be removed, so reparent it to nothing. + joint_data->reparent_to(nullptr); + + // Determine what kind of node it is we're removing, for the user's + // information. + if ((user_data->_flags & EggOptcharUserData::F_identity) != 0) { + num_identity++; + } else if ((user_data->_flags & EggOptcharUserData::F_static) != 0) { + num_static++; + } else if ((user_data->_flags & EggOptcharUserData::F_empty) != 0) { + num_empty++; + } else { + num_other++; + } + removed_any = true; + + } else { + // This joint will be preserved, but maybe its parent will change. + EggJointData *best_parent = find_best_parent(joint_data->get_parent()); + joint_data->reparent_to(best_parent); + if ((user_data->_flags & EggOptcharUserData::F_expose) != 0) { + joint_data->expose(); + } else if ((user_data->_flags & EggOptcharUserData::F_suppress) != 0) { + joint_data->expose(EggGroup::DC_none); + } + num_kept++; + } + } + + if (num_joints == num_kept) { + nout << char_data->get_name() << ": keeping " << num_joints + << " joints.\n"; + } else { + nout << setw(5) << num_joints + << " original joints in " << char_data->get_name() + << "\n"; + if (num_identity != 0) { + nout << setw(5) << num_identity << " identity joints\n"; + } + if (num_static != 0) { + nout << setw(5) << num_static << " unanimated joints\n"; + } + if (num_empty != 0) { + nout << setw(5) << num_empty << " empty joints\n"; + } + if (num_other != 0) { + nout << setw(5) << num_other << " other joints\n"; + } + nout << " ----\n" + << setw(5) << num_kept << " joints remaining\n\n"; + } + } + + return removed_any; +} + +/** + * Searches for the first joint at this level or above that is not scheduled + * to be removed. This is the joint that the first child of this joint should + * be reparented to. + */ +EggJointData *EggOptchar:: +find_best_parent(EggJointData *joint_data) const { + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, joint_data->get_user_data()); + + if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) { + // Keep going. + if (joint_data->get_parent() != nullptr) { + return find_best_parent(joint_data->get_parent()); + } + } + + // This is the one! + return joint_data; +} + +/** + * Searches for the first joint at this level or above that is not static. + * This is the joint that the vertices of this joint should be moved into. + */ +EggJointData *EggOptchar:: +find_best_vertex_joint(EggJointData *joint_data) const { + if (joint_data == nullptr) { + return nullptr; + } + + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, joint_data->get_user_data()); + + if ((user_data->_flags & EggOptcharUserData::F_static) != 0) { + // Keep going. + return find_best_vertex_joint(joint_data->get_parent()); + } + + // This is the one! + return joint_data; +} + +/** + * Reparents all the joints that the user suggested on the command line. + * Returns true if any operations were performed, false otherwise. + */ +bool EggOptchar:: +apply_user_reparents() { + bool did_anything = false; + + int num_characters = _collection->get_num_characters(); + + // First, get the new joints. + StringPairs::const_iterator spi; + for (spi = _new_joints.begin(); spi != _new_joints.end(); ++spi) { + const StringPair &p = (*spi); + + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + EggJointData *node_a = char_data->find_joint(p._a); + EggJointData *node_b = char_data->get_root_joint(); + if (!p._b.empty()) { + node_b = char_data->find_joint(p._b); + } + + if (node_b == nullptr) { + nout << "No joint named " << p._b << " in " << char_data->get_name() + << ".\n"; + + } else if (node_a != nullptr) { + nout << "Joint " << p._a << " already exists in " + << char_data->get_name() << ".\n"; + + } else { + nout << "Creating new joint " << p._a << " in " + << char_data->get_name() << ".\n"; + node_a = char_data->make_new_joint(p._a, node_b); + did_anything = true; + } + } + } + + // Now get the user reparents. + for (spi = _reparent_joints.begin(); spi != _reparent_joints.end(); ++spi) { + const StringPair &p = (*spi); + + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + EggJointData *node_a = char_data->find_joint(p._a); + EggJointData *node_b = char_data->get_root_joint(); + if (!p._b.empty()) { + node_b = char_data->find_joint(p._b); + } + + if (node_b == nullptr) { + nout << "No joint named " << p._b << " in " << char_data->get_name() + << ".\n"; + } else if (node_a == nullptr) { + nout << "No joint named " << p._a << " in " << char_data->get_name() + << ".\n"; + } else { + node_a->reparent_to(node_b); + did_anything = true; + } + } + } + + if (_optimal_hierarchy) { + did_anything = true; + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + nout << "Computing optimal hierarchy for " + << char_data->get_name() << ".\n"; + char_data->choose_optimal_hierarchy(); + nout << "Done computing optimal hierarchy for " + << char_data->get_name() << ".\n"; + } + } + + return did_anything; +} + +/** + * Zeroes out the channels specified by the user on the command line. + * + * Returns true if any operation was performed, false otherwise. + */ +bool EggOptchar:: +zero_channels() { + bool did_anything = false; + int num_characters = _collection->get_num_characters(); + + StringPairs::const_iterator spi; + for (spi = _zero_channels.begin(); spi != _zero_channels.end(); ++spi) { + const StringPair &p = (*spi); + + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + EggJointData *joint_data = char_data->find_joint(p._a); + + if (joint_data == nullptr) { + nout << "No joint named " << p._a << " in " << char_data->get_name() + << ".\n"; + } else { + joint_data->zero_channels(p._b); + did_anything = true; + } + } + } + + return did_anything; +} + +/** + * Quantizes the channels specified by the user on the command line. + * + * Returns true if any operation was performed, false otherwise. + */ +bool EggOptchar:: +quantize_channels() { + bool did_anything = false; + int num_characters = _collection->get_num_characters(); + + DoubleStrings::const_iterator spi; + for (spi = _quantize_anims.begin(); spi != _quantize_anims.end(); ++spi) { + const DoubleString &p = (*spi); + + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + EggJointData *joint_data = char_data->get_root_joint(); + + if (joint_data != nullptr) { + joint_data->quantize_channels(p._b, p._a); + did_anything = true; + } + } + } + + return did_anything; +} + +/** + * Recursively walks the joint hierarchy for a particular character, + * indentifying properties of each joint. + */ +void EggOptchar:: +analyze_joints(EggJointData *joint_data, int level) { + PT(EggOptcharUserData) user_data = new EggOptcharUserData; + joint_data->set_user_data(user_data); + + if (level == 1) { + // The child joints of the root joint are deemed "top" joints. These may + // not be removed unless they are empty (because their vertices have no + // joint to be moved into). + user_data->_flags |= EggOptcharUserData::F_top; + } + + // Analyze the table of matrices for this joint, checking to see if they're + // all the same across all frames, or if any of them are different; also + // look for empty joints (that control no vertices). + int num_mats = 0; + bool different_mat = false; + bool has_vertices = false; + + int num_models = joint_data->get_num_models(); + int i; + for (i = 0; i < num_models; i++) { + if (joint_data->has_model(i)) { + EggBackPointer *model = joint_data->get_model(i); + if (model->has_vertices()) { + has_vertices = true; + } + + int num_frames = joint_data->get_num_frames(i); + + int f; + for (f = 0; f < num_frames && !different_mat; f++) { + LMatrix4d mat = joint_data->get_frame(i, f); + num_mats++; + if (num_mats == 1) { + // This is the first matrix. + user_data->_static_mat = mat; + + } else { + // This is a second or later matrix. + if (!mat.almost_equal(user_data->_static_mat, 0.0001)) { + // It's different than the first one. + different_mat = true; + } + } + } + } + } + + if (!different_mat) { + // All the mats are the same for this joint. + user_data->_flags |= EggOptcharUserData::F_static; + + if (num_mats == 0 || + user_data->_static_mat.almost_equal(LMatrix4d::ident_mat(), 0.0001)) { + // It's not only static, but it's the identity matrix. + user_data->_flags |= EggOptcharUserData::F_identity; + } + } + + if (!has_vertices) { + // There are no vertices in this joint. + user_data->_flags |= EggOptcharUserData::F_empty; + } + + int num_children = joint_data->get_num_children(); + for (i = 0; i < num_children; i++) { + analyze_joints(joint_data->get_child(i), level + 1); + } +} + +/** + * Linearly walks the slider list for a particular character, indentifying + * properties of each slider. + */ +void EggOptchar:: +analyze_sliders(EggCharacterData *char_data) { + int num_sliders = char_data->get_num_sliders(); + for (int si = 0; si < num_sliders; si++) { + EggSliderData *slider_data = char_data->get_slider(si); + + PT(EggOptcharUserData) user_data = new EggOptcharUserData; + slider_data->set_user_data(user_data); + + // Analyze the table of values for this slider, checking to see if they're + // all the same across all frames, or if any of them are different; also + // look for empty sliders (that control no vertices). + int num_values = 0; + bool different_value = false; + bool has_vertices = false; + + int num_models = slider_data->get_num_models(); + for (int i = 0; i < num_models; i++) { + if (slider_data->has_model(i)) { + EggBackPointer *model = slider_data->get_model(i); + if (model->has_vertices()) { + has_vertices = true; + } + + int num_frames = slider_data->get_num_frames(i); + + int f; + for (f = 0; f < num_frames && !different_value; f++) { + double value = slider_data->get_frame(i, f); + num_values++; + if (num_values == 1) { + // This is the first value. + user_data->_static_value = value; + + } else { + // This is a second or later value. + if (!IS_THRESHOLD_EQUAL(value, user_data->_static_value, 0.0001)) { + // It's different than the first one. + different_value = true; + } + } + } + } + } + + if (!different_value) { + // All the values are the same for this slider. + user_data->_flags |= EggOptcharUserData::F_static; + + if (num_values == 0 || IS_THRESHOLD_ZERO(user_data->_static_value, 0.0001)) { + // It's not only static, but it's the identity value. + user_data->_flags |= EggOptcharUserData::F_identity; + } + } + + if (!has_vertices) { + // There are no vertices in this slider. + user_data->_flags |= EggOptcharUserData::F_empty; + } + } +} + +/** + * Outputs a list of the joint hierarchy. + */ +void EggOptchar:: +list_joints(EggJointData *joint_data, int indent_level, bool verbose) { + // Don't list the root joint, which is artificially created when the + // character is loaded. Instead, list each child as it is encountered. + + int num_children = joint_data->get_num_children(); + for (int i = 0; i < num_children; i++) { + EggJointData *child_data = joint_data->get_child(i); + describe_component(child_data, indent_level, verbose); + + list_joints(child_data, indent_level + 2, verbose); + } +} + +/** + * Outputs a list of the joint hierarchy as a series of -p joint,parent + * commands. + */ +void EggOptchar:: +list_joints_p(EggJointData *joint_data, int &col) { + // As above, don't list the root joint. + + int num_children = joint_data->get_num_children(); + static const int max_col = 72; + + for (int i = 0; i < num_children; i++) { + EggJointData *child_data = joint_data->get_child(i); + // We send output to cout instead of nout to avoid the word-wrapping, and + // also to allow the user to redirect this easily to a file. + + string text = string(" -p ") + child_data->get_name() + + string(",") + joint_data->get_name(); + if (col == 0) { + cout << " " << text; + col = 4 + text.length(); + } else { + col += text.length(); + if (col >= max_col) { + cout << " \\\n " << text; + col = 4 + text.length(); + } else { + cout << text; + } + } + + list_joints_p(child_data, col); + } +} + +/** + * Outputs a list of the scalars. + */ +void EggOptchar:: +list_scalars(EggCharacterData *char_data, bool verbose) { + int num_sliders = char_data->get_num_sliders(); + for (int si = 0; si < num_sliders; si++) { + EggSliderData *slider_data = char_data->get_slider(si); + describe_component(slider_data, 0, verbose); + } +} + +/** + * Describes one particular slider or joint. + */ +void EggOptchar:: +describe_component(EggComponentData *comp_data, int indent_level, + bool verbose) { + // We use cout instead of nout so the user can easily redirect this to a + // file. + indent(cout, indent_level) + << comp_data->get_name(); + + if (verbose) { + EggOptcharUserData *user_data = + DCAST(EggOptcharUserData, comp_data->get_user_data()); + if (user_data->is_identity()) { + cout << " (identity)"; + } else if (user_data->is_static()) { + cout << " (static)"; + } + if (user_data->is_empty()) { + cout << " (empty)"; + } + if (user_data->is_top()) { + cout << " (top)"; + } + } + cout << "\n"; +} + +/** + * Performs all of the queued up reparenting operations. + */ +void EggOptchar:: +do_reparent() { + bool all_ok = true; + + int num_characters = _collection->get_num_characters(); + for (int ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + if (!char_data->do_reparent()) { + all_ok = false; + } + } + + if (!all_ok) { + exit(1); + } +} + +/** + * Walks through all of the loaded egg files, looking for vertices whose joint + * memberships are then quantized according to _vref_quantum. + */ +void EggOptchar:: +quantize_vertices() { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + quantize_vertices(*ei); + } +} + +/** + * Recursively walks through the indicated egg hierarchy, looking for vertices + * whose joint memberships are then quantized according to _vref_quantum. + */ +void EggOptchar:: +quantize_vertices(EggNode *egg_node) { + if (egg_node->is_of_type(EggVertexPool::get_class_type())) { + EggVertexPool *vpool = DCAST(EggVertexPool, egg_node); + EggVertexPool::iterator vi; + for (vi = vpool->begin(); vi != vpool->end(); ++vi) { + quantize_vertex(*vi); + } + + } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, egg_node); + EggGroupNode::iterator ci; + for (ci = group->begin(); ci != group->end(); ++ci) { + quantize_vertices(*ci); + } + } +} + +/** + * Quantizes the indicated vertex's joint membership. + */ +void EggOptchar:: +quantize_vertex(EggVertex *egg_vertex) { + if (egg_vertex->gref_size() == 0) { + // Never mind on this vertex. + return; + } + + // First, get a copy of the existing membership. + VertexMemberships memberships; + EggVertex::GroupRef::const_iterator gi; + double net_membership = 0.0; + for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) { + EggGroup *group = (*gi); + double membership = group->get_vertex_membership(egg_vertex); + memberships.push_back(VertexMembership(group, membership)); + net_membership += membership; + } + nassertv(net_membership != 0.0); + + // Now normalize all the memberships so the net membership is 1.0, and then + // quantize the result (if the user so requested). + double factor = 1.0 / net_membership; + net_membership = 0.0; + VertexMemberships::iterator mi; + VertexMemberships::iterator largest = memberships.begin(); + + for (mi = memberships.begin(); mi != memberships.end(); ++mi) { + if ((*largest) < (*mi)) { + // Remember the largest membership value, so we can readjust it at the + // end. + largest = mi; + } + + double value = (*mi)._membership * factor; + if (_vref_quantum != 0.0) { + value = floor(value / _vref_quantum + 0.5) * _vref_quantum; + } + (*mi)._membership = value; + + net_membership += value; + } + + // The the largest membership value gets corrected again by the roundoff + // error. + (*largest)._membership += 1.0 - net_membership; + + // Finally, walk back through and apply these computed values to the vertex. + for (mi = memberships.begin(); mi != memberships.end(); ++mi) { + (*mi)._group->set_vertex_membership(egg_vertex, (*mi)._membership); + } +} + +/** + * Recursively walks the indicated egg hierarchy, looking for groups that + * match one of the group names in _flag_groups, and renaming geometry + * appropriately. + */ +void EggOptchar:: +do_flag_groups(EggGroupNode *egg_group) { + bool matched = false; + string name; + FlagGroups::const_iterator fi; + for (fi = _flag_groups.begin(); + fi != _flag_groups.end() && !matched; + ++fi) { + const FlagGroupsEntry &entry = (*fi); + Globs::const_iterator si; + for (si = entry._groups.begin(); + si != entry._groups.end() && !matched; + ++si) { + if ((*si).matches(egg_group->get_name())) { + matched = true; + if (!entry._name.empty()) { + name = entry._name; + } else { + name = egg_group->get_name(); + } + } + } + } + + if (matched) { + // Ok, this group matched one of the user's command-line renames. Rename + // all the primitives in this group and below to the indicated name; this + // will expose the primitives through the character loader. + rename_primitives(egg_group, name); + } + + // Now recurse on children. + EggGroupNode::iterator gi; + for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, child); + do_flag_groups(group); + } + } +} + +/** + * Rename all the joints named with the -rename command-line option. + */ +void EggOptchar:: +rename_joints() { + for (StringPairs::iterator spi = _rename_joints.begin(); + spi != _rename_joints.end(); + ++spi) { + const StringPair &sp = (*spi); + int num_characters = _collection->get_num_characters(); + int ci; + for (ci = 0; ci < num_characters; ++ci) { + EggCharacterData *char_data = _collection->get_character(ci); + EggJointData *joint = char_data->find_joint(sp._a); + if (joint != nullptr) { + nout << "Renaming joint " << sp._a << " to " << sp._b << "\n"; + joint->set_name(sp._b); + + int num_models = joint->get_num_models(); + for (int mn = 0; mn < num_models; ++mn) { + if (joint->has_model(mn)) { + EggBackPointer *model = joint->get_model(mn); + model->set_name(sp._b); + } + } + + } else { + nout << "Couldn't find joint " << sp._a << "\n"; + } + } + } +} + +/** + * Recursively walks the indicated egg hierarchy, renaming geometry to the + * indicated name. + */ +void EggOptchar:: +change_dart_type(EggGroupNode *egg_group, const string &new_dart_type) { + EggGroupNode::iterator gi; + for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, child); + if (child->is_of_type(EggGroup::get_class_type())) { + EggGroup *gr = DCAST(EggGroup, child); + EggGroup::DartType dt = gr->get_dart_type(); + if(dt != EggGroup::DT_none) { + EggGroup::DartType newDt = gr->string_dart_type(new_dart_type); + gr->set_dart_type(newDt); + } + } + change_dart_type(group, new_dart_type); + } + } +} + + +/** + * Recursively walks the indicated egg hierarchy, renaming geometry to the + * indicated name. + */ +void EggOptchar:: +rename_primitives(EggGroupNode *egg_group, const string &name) { + EggGroupNode::iterator gi; + for (gi = egg_group->begin(); gi != egg_group->end(); ++gi) { + EggNode *child = (*gi); + + if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, child); + rename_primitives(group, name); + + } else if (child->is_of_type(EggPrimitive::get_class_type())) { + child->set_name(name); + } + } +} + +/** + * Generates the preload tables for each model. + */ +void EggOptchar:: +do_preload() { + // First, build up the list of AnimPreload entries, one for each animation + // file. + PT(EggGroup) anim_group = new EggGroup("preload"); + + int num_characters = _collection->get_num_characters(); + int ci; + for (ci = 0; ci < num_characters; ++ci) { + EggCharacterData *char_data = _collection->get_character(ci); + + int num_models = char_data->get_num_models(); + for (int mn = 0; mn < num_models; ++mn) { + EggNode *root = char_data->get_model_root(mn); + if (root->is_of_type(EggTable::get_class_type())) { + // This model represents an animation. + EggData *data = char_data->get_egg_data(mn); + string basename = data->get_egg_filename().get_basename_wo_extension(); + PT(EggAnimPreload) anim_preload = new EggAnimPreload(basename); + + int mi = char_data->get_model_index(mn); + anim_preload->set_num_frames(char_data->get_num_frames(mi)); + double frame_rate = char_data->get_frame_rate(mi); + if (frame_rate != 0.0) { + anim_preload->set_fps(frame_rate); + } + + anim_group->add_child(anim_preload); + } + } + } + + // Now go back through and copy the preload tables into each of the model + // files. + for (ci = 0; ci < num_characters; ++ci) { + EggCharacterData *char_data = _collection->get_character(ci); + + int num_models = char_data->get_num_models(); + for (int mn = 0; mn < num_models; ++mn) { + EggNode *root = char_data->get_model_root(mn); + if (root->is_of_type(EggGroup::get_class_type())) { + // This is a model file. Copy in the table. + EggGroup *model_root = DCAST(EggGroup, root); + EggGroup::const_iterator ci; + for (ci = anim_group->begin(); ci != anim_group->end(); ++ci) { + EggAnimPreload *anim_preload = DCAST(EggAnimPreload, *ci); + PT(EggAnimPreload) new_anim_preload = new EggAnimPreload(*anim_preload); + model_root->add_child(new_anim_preload); + } + } + } + } +} + +/** + * Sets the initial pose for the character(s). + */ +void EggOptchar:: +do_defpose() { + // Split out the defpose parameter. + Filename egg_filename; + size_t comma = _defpose.find(','); + egg_filename = _defpose.substr(0, comma); + + string frame_str; + if (comma != string::npos) { + frame_str = _defpose.substr(comma + 1); + } + frame_str = trim(frame_str); + int frame = 0; + if (!frame_str.empty()) { + if (!string_to_int(frame_str, frame)) { + nout << "Invalid integer in -defpose: " << frame_str << "\n"; + return; + } + } + + // Now find the named animation file in our egg list. + int egg_index = -1; + int num_eggs = _collection->get_num_eggs(); + int i; + + // First, look for an exact match. + for (i = 0; i < num_eggs && egg_index == -1; ++i) { + if (_collection->get_egg(i)->get_egg_filename() == egg_filename) { + egg_index = i; + } + } + + // Then, look for an inexact match. + string egg_basename = egg_filename.get_basename_wo_extension(); + for (i = 0; i < num_eggs && egg_index == -1; ++i) { + if (_collection->get_egg(i)->get_egg_filename().get_basename_wo_extension() == egg_basename) { + egg_index = i; + } + } + + if (egg_index == -1) { + // No joy. + nout << "Egg file " << egg_filename << " named in -defpose, but does not appear on command line.\n"; + return; + } + + EggData *egg_data = _collection->get_egg(egg_index); + + if (_collection->get_num_models(egg_index) == 0) { + nout << "Egg file " << egg_filename << " does not include any model or animation.\n"; + return; + } + + // Now get the first model (or animation) named by this egg file. + int mi = _collection->get_first_model_index(egg_index); + EggCharacterData *ch = _collection->get_character_by_model_index(mi); + EggJointData *root_joint = ch->get_root_joint(); + + int anim_index = -1; + for (i = 0; i < ch->get_num_models() && anim_index == -1; ++i) { + if (ch->get_egg_data(i) == egg_data) { + anim_index = i; + } + } + + // This couldn't possibly fail, since we already checked this above. + nassertv(anim_index != -1); + + // Now we can recursively apply the default pose to the hierarchy. + root_joint->apply_default_pose(anim_index, frame); +} + +int main(int argc, char *argv[]) { + EggOptchar prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/egg-optchar/eggOptchar.h b/pandatool/src/egg-optchar/eggOptchar.h new file mode 100644 index 00000000..30ea4cdd --- /dev/null +++ b/pandatool/src/egg-optchar/eggOptchar.h @@ -0,0 +1,129 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggOptchar.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef EGGOPTCHAR_H +#define EGGOPTCHAR_H + +#include "pandatoolbase.h" + +#include "eggCharacterFilter.h" +#include "luse.h" + +#include "pvector.h" +#include "vector_string.h" +#include "globPattern.h" + +class EggCharacterData; +class EggComponentData; +class EggJointData; +class EggSliderData; +class EggGroupNode; + +/** + * Performs basic optimizations of a character model and its associated + * animations, by analyzing the animation tables and removing unneeded joints + * and/or morphs. Can also be used to restructure the character hierarchy. + */ +class EggOptchar : public EggCharacterFilter { +public: + EggOptchar(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + static bool dispatch_vector_string_pair(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_name_components(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_double_components(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_flag_groups(const std::string &opt, const std::string &arg, void *var); + + void determine_removed_components(); + void move_vertices(); + bool process_joints(); + EggJointData *find_best_parent(EggJointData *joint_data) const; + EggJointData *find_best_vertex_joint(EggJointData *joint_data) const; + + bool apply_user_reparents(); + bool zero_channels(); + bool quantize_channels(); + void analyze_joints(EggJointData *joint_data, int level); + void analyze_sliders(EggCharacterData *char_data); + void list_joints(EggJointData *joint_data, int indent_level, bool verbose); + void list_joints_p(EggJointData *joint_data, int &col); + void list_scalars(EggCharacterData *char_data, bool verbose); + void describe_component(EggComponentData *comp_data, int indent_level, + bool verbose); + void do_reparent(); + + void quantize_vertices(); + void quantize_vertices(EggNode *egg_node); + void quantize_vertex(EggVertex *egg_vertex); + + void do_flag_groups(EggGroupNode *egg_group); + void rename_joints(); + void rename_primitives(EggGroupNode *egg_group, const std::string &name); + void change_dart_type(EggGroupNode *egg_group, const std::string &new_dart_type); + void do_preload(); + void do_defpose(); + + bool _list_hierarchy; + bool _list_hierarchy_v; + bool _list_hierarchy_p; + bool _preload; + bool _keep_all; + + class StringPair { + public: + std::string _a; + std::string _b; + }; + typedef pvector StringPairs; + StringPairs _new_joints; + StringPairs _reparent_joints; + StringPairs _zero_channels; + StringPairs _rename_joints; + + vector_string _keep_components; + vector_string _drop_components; + vector_string _expose_components; + vector_string _suppress_components; + + std::string _dart_type; + + class DoubleString { + public: + double _a; + std::string _b; + }; + typedef pvector DoubleStrings; + DoubleStrings _quantize_anims; + + typedef pvector Globs; + + class FlagGroupsEntry { + public: + Globs _groups; + std::string _name; + }; + typedef pvector FlagGroups; + FlagGroups _flag_groups; + + std::string _defpose; + + bool _optimal_hierarchy; + double _vref_quantum; +}; + +#endif diff --git a/pandatool/src/egg-optchar/eggOptcharUserData.I b/pandatool/src/egg-optchar/eggOptcharUserData.I new file mode 100644 index 00000000..47f71ac4 --- /dev/null +++ b/pandatool/src/egg-optchar/eggOptcharUserData.I @@ -0,0 +1,79 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggOptcharUserData.I + * @author drose + * @date 2003-07-18 + */ + +/** + * + */ +INLINE EggOptcharUserData:: +EggOptcharUserData() { + _flags = 0; + _static_mat = LMatrix4d::ident_mat(); + _static_value = 0.0; +} + + +/** + * + */ +INLINE EggOptcharUserData:: +EggOptcharUserData(const EggOptcharUserData ©) : + EggUserData(copy), + _flags(copy._flags), + _static_mat(copy._static_mat), + _static_value(copy._static_value) +{ +} + + +/** + * + */ +INLINE void EggOptcharUserData:: +operator = (const EggOptcharUserData ©) { + EggUserData::operator = (copy); + _flags = copy._flags; + _static_mat = copy._static_mat; + _static_value = copy._static_value; +} + +/** + * + */ +INLINE bool EggOptcharUserData:: +is_static() const { + return (_flags & F_static) != 0; +} + +/** + * + */ +INLINE bool EggOptcharUserData:: +is_identity() const { + return (_flags & F_identity) != 0; +} + +/** + * + */ +INLINE bool EggOptcharUserData:: +is_empty() const { + return (_flags & F_empty) != 0; +} + +/** + * + */ +INLINE bool EggOptcharUserData:: +is_top() const { + return (_flags & F_top) != 0; +} diff --git a/pandatool/src/egg-optchar/eggOptcharUserData.cxx b/pandatool/src/egg-optchar/eggOptcharUserData.cxx new file mode 100644 index 00000000..ecb6b32c --- /dev/null +++ b/pandatool/src/egg-optchar/eggOptcharUserData.cxx @@ -0,0 +1,16 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggOptcharUserData.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "eggOptcharUserData.h" + +TypeHandle EggOptcharUserData::_type_handle; diff --git a/pandatool/src/egg-optchar/eggOptcharUserData.h b/pandatool/src/egg-optchar/eggOptcharUserData.h new file mode 100644 index 00000000..2f12aae9 --- /dev/null +++ b/pandatool/src/egg-optchar/eggOptcharUserData.h @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggOptcharUserData.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef EGGOPTCHARUSERDATA_H +#define EGGOPTCHARUSERDATA_H + +#include "pandatoolbase.h" +#include "eggUserData.h" +#include "luse.h" + +/** + * This class contains extra user data which is piggybacked onto EggGroup + * objects for the purpose of the maya converter. + */ +class EggOptcharUserData : public EggUserData { +public: + INLINE EggOptcharUserData(); + INLINE EggOptcharUserData(const EggOptcharUserData ©); + INLINE void operator = (const EggOptcharUserData ©); + + INLINE bool is_static() const; + INLINE bool is_identity() const; + INLINE bool is_empty() const; + INLINE bool is_top() const; + + enum Flags { + F_static = 0x0001, + F_identity = 0x0002, + F_empty = 0x0004, + F_top = 0x0008, + F_remove = 0x0010, + F_expose = 0x0020, + F_suppress = 0x0040, + }; + int _flags; + LMatrix4d _static_mat; + double _static_value; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggUserData::init_type(); + register_type(_type_handle, "EggOptcharUserData", + EggUserData::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "eggOptcharUserData.I" + +#endif diff --git a/pandatool/src/egg-optchar/vertexMembership.I b/pandatool/src/egg-optchar/vertexMembership.I new file mode 100644 index 00000000..f001b1d9 --- /dev/null +++ b/pandatool/src/egg-optchar/vertexMembership.I @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vertexMembership.I + * @author drose + * @date 2003-07-21 + */ + +/** + * + */ +INLINE VertexMembership:: +VertexMembership(EggGroup *group, double membership) : + _group(group), + _membership(membership) +{ +} + +/** + * + */ +INLINE VertexMembership:: +VertexMembership(const VertexMembership ©) : + _group(copy._group), + _membership(copy._membership) +{ +} + +/** + * + */ +INLINE void VertexMembership:: +operator = (const VertexMembership ©) { + _group = copy._group; + _membership = copy._membership; +} + +/** + * + */ +INLINE bool VertexMembership:: +operator < (const VertexMembership &other) const { + if (_membership != other._membership) { + return _membership < other._membership; + } + return _group < other._group; +} diff --git a/pandatool/src/egg-optchar/vertexMembership.cxx b/pandatool/src/egg-optchar/vertexMembership.cxx new file mode 100644 index 00000000..d7eab777 --- /dev/null +++ b/pandatool/src/egg-optchar/vertexMembership.cxx @@ -0,0 +1,14 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vertexMembership.cxx + * @author drose + * @date 2003-07-21 + */ + +#include "vertexMembership.h" diff --git a/pandatool/src/egg-optchar/vertexMembership.h b/pandatool/src/egg-optchar/vertexMembership.h new file mode 100644 index 00000000..31cc16ae --- /dev/null +++ b/pandatool/src/egg-optchar/vertexMembership.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vertexMembership.h + * @author drose + * @date 2003-07-21 + */ + +#ifndef VERTEXMEMBERSHIP_H +#define VERTEXMEMBERSHIP_H + +#include "pandatoolbase.h" + +#include "pvector.h" + +class EggGroup; + +/** + * This class is used to help EggOptchar quantize the membership of one vertex + * among its various groups. + */ +class VertexMembership { +public: + INLINE VertexMembership(EggGroup *group, double membership); + INLINE VertexMembership(const VertexMembership ©); + INLINE void operator = (const VertexMembership ©); + + INLINE bool operator < (const VertexMembership &other) const; + + EggGroup *_group; + double _membership; +}; + +typedef pvector VertexMemberships; + +#include "vertexMembership.I" + +#endif diff --git a/pandatool/src/egg-palettize/CMakeLists.txt b/pandatool/src/egg-palettize/CMakeLists.txt new file mode 100644 index 00000000..9af14457 --- /dev/null +++ b/pandatool/src/egg-palettize/CMakeLists.txt @@ -0,0 +1,20 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG) + return() +endif() + +add_executable(egg-palettize eggPalettize.cxx eggPalettize.h) +target_link_libraries(egg-palettize p3palettizer p3eggbase p3progbase) +install(TARGETS egg-palettize EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_library(p3txafile txaFileFilter.cxx txaFileFilter.h txaFileFilter.I) +set_target_properties(p3txafile PROPERTIES DEFINE_SYMBOL BUILDING_MISC) +target_link_libraries(p3txafile PRIVATE p3palettizer) +if(BUILD_SHARED_LIBS) + # We can't install this if we're doing a static build, because it depends on + # a static library that isn't installed. + install(TARGETS p3txafile EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() diff --git a/pandatool/src/egg-palettize/eggPalettize.cxx b/pandatool/src/egg-palettize/eggPalettize.cxx new file mode 100644 index 00000000..385dd789 --- /dev/null +++ b/pandatool/src/egg-palettize/eggPalettize.cxx @@ -0,0 +1,878 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggPalettize.cxx + * @author drose + * @date 2000-11-28 + */ + +#include "eggPalettize.h" +#include "palettizer.h" +#include "eggFile.h" +#include "pal_string_utils.h" +#include "filenameUnifier.h" + +#include "dcast.h" +#include "eggData.h" +#include "bamFile.h" +#include "pnotify.h" +#include "notifyCategory.h" +#include "notifySeverity.h" + +#include + +/** + * + */ +EggPalettize:: +EggPalettize() : EggMultiFilter(true) { + set_program_brief("pack textures from various .egg models into palette images"); + set_program_description + ("egg-palettize attempts to pack several texture maps from various models " + "together into one or more palette images, for improved rendering performance " + "and ease of texture management. It can also resize textures and convert " + "them to another image file format, whether or not they are actually " + "placed on a palette, and can manage some " + "simple texture properties, like mipmapping and rendering " + "format.\n\n" + + "egg-palettize reads a texture attributes file, usually named " + "textures.txa, which contains instructions from the user about " + "resizing particular textures. Type egg-palettize -H for an " + "introduction to the syntax of this file.\n\n" + + "The palettization information from previous runs is recorded in a file " + "named textures.boo (assuming the attributes file is named " + "textures.txa); a complete record of every egg file and every texture " + "that has been referenced is kept here. This allows the program " + "to intelligently manage the multiple egg files that may reference " + "the textures in question."); + + + clear_runlines(); + add_runline("[opts] file.egg [file.egg ...]"); + + // We always have EggMultiBase's -f on: force complete load. In fact, we + // use -f for our own purposes, below. + remove_option("f"); + _force_complete = true; + + add_option + ("af", "filename", 0, + "Read the indicated file as the .txa file. The default is textures.txa.", + &EggPalettize::dispatch_filename, &_got_txa_filename, &_txa_filename); + + add_option + ("a", "filename", 0, + "Deprecated option. This is the same as -af.", + &EggPalettize::dispatch_filename, &_got_txa_filename, &_txa_filename); + + add_option + ("as", "script", 0, + "Accept the script specified on the command line as the contents of the " + ".txa file, instead of reading a file on disk. This implies -nodb and " + "-opt.", + &EggPalettize::dispatch_string, &_got_txa_script, &_txa_script); + + add_option + ("nodb", "", 0, + "Don't read or record the state information to a .boo file. By default, " + "the palettization information is recorded so it can be preserved " + "between multiple invocations of egg-palettize. If you specify this " + "parameter, all the egg files to be palettized together must be " + "named at the same time. This also implies -opt, since there's no point " + "in not making an optimal packing if you won't be preserving the " + "state for future adjustments.", + &EggPalettize::dispatch_none, &_nodb); + + add_option + ("tn", "pattern", 0, + "Specify the name to generate for each palette image. The string should " + "contain %g for the group name, %p for the page name, and %i for the " + "index within the page. The extension is inferred from the image " + "type. The default is '%g_palette_%p_%i'.", + &EggPalettize::dispatch_string, &_got_generated_image_pattern, + &_generated_image_pattern); + + add_option + ("pi", "", 0, + "Do not process anything, but instead report the detailed palettization " + "information written in the state file.", + &EggPalettize::dispatch_none, &_report_pi); + + add_option + ("s", "", 0, + "Do not process anything, but report statistics on palette " + "and texture utilization from the state file.", + &EggPalettize::dispatch_none, &_report_statistics); + + add_option + ("R", "", 0, + "Remove the named egg files from the previously-generated state data " + "file.", + &EggPalettize::dispatch_none, &_remove_eggs); + + // We redefine -d using add_option() instead of redescribe_option() so it + // gets listed along with these other options that relate. + add_option + ("d", "dirname", 0, + "The directory in which to write the palettized egg files. This is " + "only necessary if more than one egg file is processed at the same " + "time; if it is included, each egg file will be processed and written " + "into the indicated directory.", + &EggPalettize::dispatch_filename, &_got_output_dirname, &_output_dirname); + add_option + ("dm", "dirname", 0, + "The directory in which to place all maps: generated palettes, " + "as well as images which were not placed on palettes " + "(but may have been resized). If this contains the string %g, " + "this will be replaced with the 'dir' string associated with a " + "palette group; see egg-palettize -H.", + &EggPalettize::dispatch_string, &_got_map_dirname, &_map_dirname); + add_option + ("ds", "dirname", 0, + "The directory to write palette shadow images to. These are working " + "copies of the palette images, useful when the palette image type is " + "a lossy-compression type like JPEG; you can avoid generational loss " + "of quality on the palette images with each pass through the palettes " + "by storing these extra shadow images in a lossless image type. This " + "directory is only used if the :shadowtype keyword appears in the .txa " + "file.", + &EggPalettize::dispatch_filename, &_got_shadow_dirname, &_shadow_dirname); + add_option + ("dr", "dirname", 0, + "The directory to make map filenames relative to when writing egg " + "files. If specified, this should be an initial substring of -dm.", + &EggPalettize::dispatch_filename, &_got_rel_dirname, &_rel_dirname); + add_option + ("g", "group", 0, + "The default palette group that egg files will be assigned to if they " + "are not explicitly assigned to any other group.", + &EggPalettize::dispatch_string, &_got_default_groupname, &_default_groupname); + add_option + ("gdir", "name", 0, + "The \"dir\" string to associate with the default palette group " + "specified with -g, if no other dir name is given in the .txa file.", + &EggPalettize::dispatch_string, &_got_default_groupdir, &_default_groupdir); + + add_option + ("all", "", 0, + "Consider all the textures referenced in all egg files that have " + "ever been palettized, not just the egg files that appear on " + "the command line.", + &EggPalettize::dispatch_none, &_all_textures); + add_option + ("egg", "", 0, + "Regenerate all egg files that need modification, even those that " + "aren't named on the command line.", + &EggPalettize::dispatch_none, &_redo_eggs); + add_option + ("redo", "", 0, + "Force a regeneration of each image from its original source(s). " + "When used in conjunction with -egg, this also forces each egg file to " + "be regenerated.", + &EggPalettize::dispatch_none, &_redo_all); + add_option + ("opt", "", 0, + "Force an optimal packing. By default, textures are added to " + "existing palettes without disturbing them, which can lead to " + "suboptimal packing. Including this switch forces the palettes " + "to be rebuilt if necessary to optimize the packing, but this " + "may invalidate other egg files which share this palette.", + &EggPalettize::dispatch_none, &_optimal); + add_option + ("omitall", "", 0, + "Re-enables the flag to omit all textures. This flag is normally on " + "by default, causing nothing actually to be palettized, until the " + "first time egg-palettize is run with the -opt flag, which turns off " + "the omitall flag and thenceforth allows textures to be combined " + "into palettes. Specifying this flag restores the original behavior " + "of keeping every texture as a separate image (which is convenient for " + "development).", + &EggPalettize::dispatch_none, &_omitall); + + // This isn't even implemented yet. Presently, we never lock anyway. + // Dangerous, but hard to implement reliable file locking across NFSSamba + // and between multiple OS's. + /* + add_option + ("nolock", "", 0, + "Don't attempt to grab a file lock on the .txa file. Use " + "with extreme caution, as multiple processes running on the same " + ".txa file may overwrite each other. Use this only if the lock " + "cannot be achieved for some reason.", + &EggPalettize::dispatch_none, &_dont_lock_txa); + */ + + add_option + ("H", "", 0, + "Describe the syntax of the attributes file.", + &EggPalettize::dispatch_none, &_describe_input_file); + + _txa_filename = "textures.txa"; +} + + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggPalettize:: +handle_args(ProgramBase::Args &args) { + if (_describe_input_file) { + describe_input_file(); + exit(1); + } + + if (_remove_eggs) { + // If we're removing these egg files from the database, we don't want to + // try to load them up. Instead, just save the filenames. + _remove_egg_list = args; + return true; + } + + // Otherwise, load the named egg files up normally. + return EggMultiFilter::handle_args(args); +} + +/** + * + */ +void EggPalettize:: +describe_input_file() { + nout << + "An attributes file consists mostly of lines describing desired sizes of " + "texture maps. The format resembles, but is not identical to, that of " + "the qtess input file. Examples:\n\n" + + " texturename.rgb : 64 64\n" + " texture-a.rgb texture-b.rgb : 32 16 margin 2\n" + " *.rgb : 50% cont\n" + " eyelids.rgb : 16 16 omit\n\n" + + "In general, each line consists of one or more filenames (and can " + "contain shell globbing characters like '*' or '?'), and a colon " + "followed by a size request. For each texture appearing in an egg " + "file, the input list is scanned from the beginning and the first " + "line that matches the filename defines the size of the texture, as " + "well as other properties associated with the texture.\n\n" + + "A size request is most often a pair of numbers, giving a specific x y " + "size of the texture. A third number may also be supplied, giving a " + "specific number of channels to convert to (for instance, to force an " + "image to a 64x64 grayscale image, set its size to 64 64 1). " + "Alternatively, a percentage scaling may be specified, e.g. 30%. The " + "requested size need not be a power of 2.\n\n" + + "Other valid keywords that may be specified on the same line with the " + "texture are:\n\n"; + + show_text(" omit", 10, + "This indicates that the texture should not be placed on any " + "palette image. It may still be resized, and it will in any " + "case be copied into the install directory.\n\n"); + + show_text(" margin i", 10, + "This specifies the number of pixels that should be written " + "around the border of the texture when it is placed in a " + "palette image; i is the integer number of pixels. The " + "use of a margin helps cut down on color bleed " + "from neighboring images. If the texture does " + "not end up placed in a palette image, the " + "margin is not used. If not specified, the default margin is " + "used, which is specified by the :margin command (see below).\n\n"); + + show_text(" coverage f", 10, + "This parameter specifies the maximum coverage to allow for this " + "particular texture before rejecting it " + "from the palette. If not specified, the default is " + "specified by the :coverage command (see below).\n\n"); + + nout << " nearest\n" + << " linear\n"; + show_text(" mipmap", 10, + "One of these options may be used to force the texture to use " + "a particular minfilter/magfilter sampling mode. If this is not " + "specified, the sampling mode specified in the egg file is " + "used. Textures that use different sampling modes cannot " + "be placed together on the same palette images.\n\n"); + + show_text(" rgba", 10, + "This specifies format 'rgba' should be in effect for this " + "particular texture. Any valid egg texture format, such as " + "rgba, rgba12, rgba8, rgb5, luminance, etc. may be specified. " + "If nothing is specified, the format specified in the egg file " + "is used. The format will automatically be downgraded to match " + "the number of channels in the texture image; e.g. rgba will " + "automatically be converted to rgb for a three-channel image. " + "As with the filter modes above, textures that use different " + "formats cannot be placed together on the same palette " + "images.\n\n"); + + show_text(" force-rgba", 10, + "This specifies a particular format, as above, that should be " + "in effect for this texture, but it will not be downgraded to " + "match the number of channels. As above, any valid egg texture " + "format may be used, e.g. force-rgba12, force-rgb5, etc.\n\n"); + + show_text(" srgb", 10, + "This specifies that this texture is in sRGB space and the format " + "should be changed to reflect that. The texture format will be " + "changed to the appropriate sRGB equivalent based on the number " + "of image channels.\n\n"); + + show_text(" keep-format", 10, + "This specifies that the image format requested by an egg file " + "should be exactly preserved, without attempting to optimize " + "it by, for instance, automatically downgrading.\n\n"); + + show_text(" generic", 10, + "Specifies that any image format requested by an egg file " + "that requests a particular bitdepth should be replaced by " + "its generic equivalent, e.g. rgba8 should become rgba.\n\n"); + + show_text(" (alpha mode)", 10, + "A particular alpha mode may be applied to a texture by naming " + "the alpha mode. This may be any valid egg alpha mode, e.g. " + "blend, binary, ms, or dual.\n\n"); + + show_text(" repeat_u, repeat_v, clamp_u, clamp_v", 10, + "Explcitly specify whether the source texture should repeat or " + "clamp in each direction. Although palette images are always " + "clamped, this will affect the pixels that are painted into " + "the palette image.\n\n"); + + show_text(" (image type)", 10, + "A texture may be converted to a particular image type, for " + "instance jpg or rgb, by naming the type. If present, this " + "overrides the :imagetype command, described below. As with " + ":imagetype, you may also specify two type names separated " + "by a comma, to indicate that a different file should be written " + "for the color and alpha components.\n\n"); + + show_text(" (group name)", 10, + "A texture may also be assigned to a specific group by naming " + "the group. The groups are defined using the :group command " + "(see below). Normally, textures are not assigned directly " + "to groups; instead, it is more useful to assign the egg files " + "they are referenced in to groups; see below.\n\n"); + + show_text(" cont", 10, + "Normally, a texture file (or egg file) scans the lines in the " + "attributes file from the top, and stops on the first line that " + "matches its name. If the keyword 'cont' is included on the " + "line, however, the texture will apply the properties given " + "on the line, and then continue scanning. This trick may be " + "used to specify general parameters for all files while still " + "allowing the texture to match a more specific line below.\n\n"); + + nout << + "The attributes file may also assign egg files to various " + "named palette groups. The syntax is similar to the above:\n\n" + + " car-blue.egg : main\n" + " road.egg house.egg : main\n" + " plane.egg : phase_2 main\n" + " *.egg : phase_2\n\n" + + "Any number of egg files may be named on one line, and the set of " + "named egg files may be simultaneously assigned to one or more groups. " + "Each group must have been previously defined using the :group command " + "(see below). Each texture that is referenced by a given " + "egg file will be palettized " + "into at least one of the groups assigned to the egg file.\n\n" + + "Finally, there are a number of special commands that may appear in the " + "attributes file; some of these have been alluded to in the above " + "comments. These commands typically specify global parameters or " + "palettization options. The command names begin with a colon to " + "distinguish them from other kinds of lines. Each command must " + "appear on a line by itself. The commands are:\n\n"; + + show_text(" :palette xsize ysize", 10, + "This specifies the size of the palette images to be " + "created. The default is 512 by 512.\n\n"); + + show_text(" :margin msize", 10, + "This specifies the amount of default margin to apply to all " + "textures that are placed within a palette image. The margin " + "is a number of additional pixels that are written around the " + "texture image to help prevent color bleeding between " + "neighboring images within the same palette. The default " + "is 2.\n\n"); + + show_text(" :background r g b a", 10, + "Specifies the background color of the generated palette " + "images. Normally, this is black, and it doesn't matter much " + "since the background color is, by definition, the color " + "of the palette images where nothing is used.\n\n"); + + show_text(" :coverage area", 10, + "The 'coverage' of a texture refers to the fraction of " + "the area in the texture image that is actually used, according " + "to the UV's that appear in the various egg files. If a texture's " + "coverage is less than 1, only some of the texture image is used " + "(and only this part will be written to the palette). If the " + "coverage is greater than 1, the texture repeats that number of " + "times. A repeating texture may still be palettized by writing " + "the required number of copies into the palette image, according " + "to the coverage area.\n\n" + + "This command specifies the maximum coverage to allow for any " + "texture before rejecting it from the palette. It may be any " + "floating-point number greater than zero. Set this to 1 " + "to avoid palettizing repeating textures altogether. This may " + "also be overridden for a particular texture using the 'coverage' " + "keyword on the texture line.\n\n"); + + show_text(" :powertwo flag", 10, + "Specifies whether textures should be forced to a power of two " + "size when they are not placed within a palette. Use 1 for true, " + "to force textures to a power of two; or 0 to leave them exactly " + "the size they are specified. The default is true.\n\n"); + + show_text(" :round fraction fuzz", 10, + "When the coverage area is computed, it may optionally be " + "rounded up to the next sizeable unit before placing the " + "texture within the palette. This helps reduce constant " + "repalettization caused by slight differences in coverage " + "between egg files. For instance, say file a.egg references a " + "texture with a coverage of 0.91, and then later file b.egg " + "is discovered to reference the same texture with a coverage of " + "0.92. If the texture was already palettized with the original " + "coverage of 0.91, it must now be moved in the palette.\n\n" + + "Rounding the coverage area up to some fixed unit reduces this " + "problem. For instance, if you specified a value 0.5 for " + "fraction in the above command, it would round both of these " + "values up to the next half-unit, or 1.0.\n\n" + + "The second number is a fuzz factor, and should be a small " + "number; if the coverage area is just slightly larger than " + "the last unit (within the fuzz factor), it is rounded down " + "instead of up. This is intended to prevent UV coordinates " + "that are just slightly out of the range [0, 1] (which happens " + "fairly often) from forcing the palettization area all the " + "way up to the next stop.\n\n" + + "The default if this is unspecified is 0.1 0.01. That is, " + "round up to the next tenth, unless within a hundredth of the " + "last tenth. To disable rounding, specify ':round no'. " + "Rounding is implicitly disabled when you run with the -opt " + "command line option.\n\n"); + + show_text(" :remap (never | group | poly)", 10, + "Sometimes two different parts of an egg file may reference " + "different regions of a repeating texture. For instance, " + "group A may reference UV coordinate values ranging from (0,5) " + "to (1,6), for a coverage of 1.0, while group B references " + "values ranging from (0,2) to (1,4), for a coverage of 2.0. " + "The maximum coverage used is only 2.0, and thus the texture " + "only needs to appear in the palette twice, but the total range " + "of UV's is from (0,2) to (1,6), causing an apparent coverage " + "of 4.0.\n\n" + + "It's possible for egg-palettize to reduce this kind of mistake " + "by remapping both groups of UV's so that they overlap. This " + "parameter specifies how this operation should be done. If " + "the option is 'never', remapping will not be performed; if " + "'group', entire groups will be remapped as a unit, if 'poly', " + "individual polygons within a group may be remapped. This last " + "option provides the greatest minimization of UV coverage, " + "but possibly at the expense of triangle strips in the resulting " + "model (since some vertices can no longer be shared).\n\n" + + "Sometimes, it may be necessary to be more restrictive on " + "character geometry than on non-character geometry, because " + "the cost of adding additional vertices on characters is " + "greater. You can specify a different kind of remapping for " + "characters only, by using the keyword 'char' on the same line, " + "e.g. ':remap group char never'.\n\n" + + "The default remap mode for all geometry, character or otherwise, " + "if no remap mode is specified is 'poly'.\n\n"); + + show_text(" :imagetype type[,alpha_type]", 10, + "This specifies the default type of image file that should be " + "generated for each palette image and for each unplaced texture " + "copied into the install directory. This may be overridden for " + "a particular texture by specifying the image type on the " + "texture line.\n\n" + + "If two image type names separate by a comma are given, it means " + "to generate a second file of the second type for the alpha " + "channel, for images that require an alpha channel. This allows " + "support for image file formats that do not support alpha " + "(for instance, JPEG).\n\n"); + + show_text(" :shadowtype type[,alpha_type]", 10, + "When generating palette images, egg-palettize sometimes has to " + "read and write the same palette image repeatedly. If the " + "palette image is stored in a lossy file format (like JPEG, see " + ":imagetype), this can eventually lead to degradation of the " + "palette images. As a workaround, egg-palettize can store " + "its working copies of the palette images in lossless shadow " + "images. Specify this to enable this feature; give it the " + "name of a lossless image file format. The shadow images will " + "be written to the directory specified by -ds on the command " + "line.\n\n"); + + show_text(" :group groupname [dir dirname] [on group1 group2 ...] [includes group1 group2 ...]", 10, + "This defines a palette group, a logical division of textures. " + "Each texture is assigned to one or more palette groups before " + "being placed in any palette image; the palette images are " + "tied to the groups.\n\n" + + "The optional parameter 'dir' specifies a directory name to " + "associate with this group. This name is substituted in for " + "the string '%g' when it appears in the map directory name " + "specified on the command line with -dm; this may be used to " + "install textures and palettes into different directories based " + "on the groups they are assigned to.\n\n" + + "Palette groups can also be hierarchically related. The " + "keyword 'on' specifies any number of groups that this " + "palette group depends on; if a texture has already been " + "assigned to one of this group's dependent groups, it will " + "not need to be assigned to this group. This also implicitly " + "specifies a dir if one has not already been specified.\n\n" + + "The keyword 'includes' names one or more groups that depend " + "on this group.\n\n"); + + show_text(" :textureswap groupname texturename0 texturename1 [texturename2 ...]", 10, + "This option builds a set of matching, interchangeable palette images. " + "All palette images in the set share the same internal texture layout. " + "The intention is to be able to swap palette images out at runtime, " + "to replace entire sets of textures on a model in one operation. " + "The textures named by this option indicate the texture images " + "which are similar to each other, and which all should be assigned " + "to the same placement on the different palette images: " + "texturename0 will be assigned to palette image 0, " + "texturename1 to the same position on palette image 1, " + "texturename2 to the same position on palette image 2, and so on. " + "To define a complete palette image, you must repeat this option " + "several times to associate all of the similar texture images.\n\n"); + + nout << + "Comments may appear freely throughout the file, and are set off by a " + "hash mark (#).\n\n"; +} + + +/** + * + */ +void EggPalettize:: +run() { + // Fiddle with the loader severity, so we don't confuse the user with + // spurious "reading" and "writing" messages about the state file. If the + // severity is currently NS_info (the default), set it to NS_warning + // instead. + Notify *notify = Notify::ptr(); + NotifyCategory *loader_cat = notify->get_category(":loader"); + if (loader_cat != nullptr && + loader_cat->get_severity() == NS_info) { + loader_cat->set_severity(NS_warning); + } + + Filename state_filename; + BamFile state_file; + + if (_got_txa_script) { + // If we got a command-line script instead of a .txa file, we won't be + // encoding a .boo file either. + _nodb = true; + + } else { + // Look for the .txa file. + if (!_txa_filename.exists() && !_got_txa_filename) { + // If we did not specify a filename, and the default filename of + // "textures.txa" doesn't exist, try looking in srcmaps, as another + // likely possibility. + Filename maybe = _txa_filename; + maybe.set_dirname("src/maps"); + if (maybe.exists()) { + _txa_filename = maybe; + } + } + + if (!_txa_filename.exists()) { + nout << FilenameUnifier::make_user_filename(_txa_filename) + << " does not exist; cannot run.\n"; + exit(1); + } + + FilenameUnifier::set_txa_filename(_txa_filename); + + state_filename = _txa_filename; + state_filename.set_extension("boo"); + } + + if (_nodb) { + // -nodb means don't attempt to read textures.boo; in fact, don't even + // bother reporting this absence to the user. + pal = new Palettizer; + + // And -nodb implies -opt. + _optimal = true; + + } else if (!state_filename.exists()) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " does not exist; starting palettization from scratch.\n"; + pal = new Palettizer; + + // By default, the -omitall flag is true from the beginning. + pal->_omit_everything = true; + + } else { + // Read the Palettizer object from the Bam file written previously. This + // will recover all of the state saved from the past session. + nout << "Reading " << FilenameUnifier::make_user_filename(state_filename) + << "\n"; + + if (!state_file.open_read(state_filename)) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " exists, but cannot be read. Perhaps you should " + << "remove it so a new one can be created.\n"; + exit(1); + } + + TypedWritable *obj = state_file.read_object(); + if (obj == nullptr || !state_file.resolve()) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " exists, but appears to be corrupt. Perhaps you " + << "should remove it so a new one can be created.\n"; + exit(1); + } + + if (!obj->is_of_type(Palettizer::get_class_type())) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " exists, but does not appear to be " + << "an egg-palettize output file. Perhaps you " + << "should remove it so a new one can be created.\n"; + exit(1); + } + + state_file.close(); + + pal = DCAST(Palettizer, obj); + + if (pal->_read_pi_version > pal->_pi_version) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " was written by a more recent version of egg-palettize " + << "than this one. You will need to update your egg-palettize.\n"; + exit(1); + } + + if (pal->_read_pi_version < pal->_min_pi_version) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " was written by an old version of egg-palettize.\n\n" + << "You will need to make undo-pal (or simply remove the file " + << FilenameUnifier::make_user_filename(state_filename) + << " and try again).\n\n"; + exit(1); + } + + if (!pal->is_valid()) { + nout << FilenameUnifier::make_user_filename(state_filename) + << " could not be properly read. You will need to remove it.\n"; + exit(1); + } + } + + pal->set_noabs(_noabs); + + if (_report_pi) { + pal->report_pi(); + exit(0); + } + + if (_report_statistics) { + pal->report_statistics(); + exit(0); + } + + bool okflag = true; + + if (_got_txa_script) { + std::istringstream txa_script(_txa_script); + pal->read_txa_file(txa_script, "command line"); + + } else { + _txa_filename.set_text(); + std::ifstream txa_file; + if (!_txa_filename.open_read(txa_file)) { + nout << "Unable to open " << _txa_filename << "\n"; + exit(1); + } + pal->read_txa_file(txa_file, _txa_filename); + } + + if (_got_generated_image_pattern) { + pal->_generated_image_pattern = _generated_image_pattern; + } + + if (_got_default_groupname) { + pal->_default_groupname = _default_groupname; + } else { + pal->_default_groupname = _txa_filename.get_basename_wo_extension(); + } + + if (_got_default_groupdir) { + pal->_default_groupdir = _default_groupdir; + } + + if (_got_map_dirname) { + pal->_map_dirname = _map_dirname; + } + if (_got_shadow_dirname) { + pal->_shadow_dirname = _shadow_dirname; + } + if (_got_rel_dirname) { + pal->_rel_dirname = _rel_dirname; + FilenameUnifier::set_rel_dirname(_rel_dirname); + } + + // We only omit solitary textures from palettes if we're running in optimal + // mode. Otherwise, we're likely to invalidate old egg files by changing a + // texture from solitary to nonsolitary state or vice-versa. + pal->_omit_solitary = _optimal; + + if (_omitall) { + pal->_omit_everything = true; + } else if (_optimal) { + pal->_omit_everything = false; + } + + pal->all_params_set(); + + // Remove any files named for removal. + Args::const_iterator ai; + for (ai = _remove_egg_list.begin(); ai != _remove_egg_list.end(); ++ai) { + Filename filename = (*ai); + pal->remove_egg_file(filename.get_basename()); + } + + // And process the egg files named for addition. + bool all_eggs_valid = true; + + std::string egg_comment = get_exec_command(); + Eggs::const_iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *egg_data = (*ei); + Filename source_filename = egg_data->get_egg_filename(); + Filename dest_filename = get_output_filename(source_filename); + std::string name = source_filename.get_basename(); + + EggFile *egg_file = pal->get_egg_file(name); + if (!egg_file->from_command_line(egg_data, source_filename, dest_filename, + egg_comment)) { + all_eggs_valid = false; + + } else { + pal->add_command_line_egg(egg_file); + } + } + + if (!all_eggs_valid) { + nout << "Errors reading egg file(s).\n"; + exit(1); + } + + if (_optimal) { + // If we're asking for an optimal packing, throw away the old packing and + // start fresh. + pal->reset_images(); + _all_textures = true; + + /* Asad: I disagree: unless :round is set to no from textures.txa, we + should always leave the _round_uvs to default. + // Also turn off the rounding-up of UV's for this purpose. + pal->_round_uvs = false; + */ + } + + if (_all_textures) { + pal->process_all(_redo_all, state_filename); + } else { + pal->process_command_line_eggs(_redo_all, state_filename); + } + + if (_optimal) { + // If we're asking for optimal packing, this also implies we want to + // resize the big empty palette images down. + pal->optimal_resize(); + } + + if (_redo_eggs) { + if (!pal->read_stale_eggs(_redo_all)) { + okflag = false; + } + } + + if (okflag) { + pal->generate_images(_redo_all); + + if (_redo_eggs) { + // generate_images() might have made a few more stale egg files + // (particularly if a texture palette changed filenames). + if (!pal->read_stale_eggs(false)) { + okflag = false; + } + } + } + + if (okflag) { + if (!pal->write_eggs()) { + okflag = false; + } + } + + if (!_nodb) { + // Make up a temporary filename to write the state file to, then move the + // state file into place. We do this in case the user interrupts us (or + // we core dump) before we're done; that way we won't leave the state file + // incompletely written. + std::string dirname = state_filename.get_dirname(); + if (dirname.empty()) { + dirname = "."; + } + Filename temp_filename = Filename::temporary(dirname, "pi"); + + if (!state_file.open_write(temp_filename) || + !state_file.write_object(pal)) { + nout << "Unable to write palettization information to " + << FilenameUnifier::make_user_filename(temp_filename) + << "\n"; + exit(1); + } + + state_file.close(); + state_filename.unlink(); + if (!temp_filename.rename_to(state_filename)) { + nout << "Unable to rename temporary file " + << FilenameUnifier::make_user_filename(temp_filename) << " to " + << FilenameUnifier::make_user_filename(state_filename) << "\n"; + exit(1); + } + } + + if (!okflag) { + exit(1); + } +} + +int +main(int argc, char *argv[]) { + EggPalettize prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/egg-palettize/eggPalettize.h b/pandatool/src/egg-palettize/eggPalettize.h new file mode 100644 index 00000000..d30a9d4f --- /dev/null +++ b/pandatool/src/egg-palettize/eggPalettize.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggPalettize.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef EGGPALETTIZE_H +#define EGGPALETTIZE_H + +#include "pandatoolbase.h" + +#include "eggMultiFilter.h" + +/** + * This is the program wrapper for egg-palettize, but it mainly serves to read + * in all the command-line parameters and then invoke the Palettizer. + */ +class EggPalettize : public EggMultiFilter { +public: + EggPalettize(); + + virtual bool handle_args(Args &args); + + void describe_input_file(); + + void run(); + + // The following parameter values specifically relate to textures and + // palettes. These values are copied to the Palettizer. + bool _got_txa_filename; + Filename _txa_filename; + bool _got_txa_script; + std::string _txa_script; + bool _nodb; + std::string _generated_image_pattern; + bool _got_generated_image_pattern; + std::string _map_dirname; + bool _got_map_dirname; + Filename _shadow_dirname; + bool _got_shadow_dirname; + Filename _rel_dirname; + bool _got_rel_dirname; + std::string _default_groupname; + bool _got_default_groupname; + std::string _default_groupdir; + bool _got_default_groupdir; + +private: + // The following values control behavior specific to this session. They're + // not saved for future sessions. + bool _report_pi; + bool _report_statistics; + bool _all_textures; + bool _optimal; + bool _omitall; + bool _redo_all; + bool _redo_eggs; + + bool _describe_input_file; + bool _remove_eggs; + Args _remove_egg_list; +}; + +#endif diff --git a/pandatool/src/egg-palettize/txaFileFilter.I b/pandatool/src/egg-palettize/txaFileFilter.I new file mode 100644 index 00000000..44cde648 --- /dev/null +++ b/pandatool/src/egg-palettize/txaFileFilter.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaFileFilter.I + * @author drose + * @date 2006-07-27 + */ diff --git a/pandatool/src/egg-palettize/txaFileFilter.cxx b/pandatool/src/egg-palettize/txaFileFilter.cxx new file mode 100644 index 00000000..d45bea3b --- /dev/null +++ b/pandatool/src/egg-palettize/txaFileFilter.cxx @@ -0,0 +1,148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaFileFilter.cxx + * @author drose + * @date 2006-07-27 + */ + +#include "txaFileFilter.h" +#include "palettizer.h" +#include "txaFile.h" +#include "textureImage.h" +#include "sourceTextureImage.h" +#include "texturePool.h" +#include "dconfig.h" +#include "configVariableFilename.h" +#include "virtualFileSystem.h" +#include "config_putil.h" + +NotifyCategoryDeclNoExport(txafile); +NotifyCategoryDef(txafile, ""); + +// A few lines to register this filter type with the TexturePool when the +// shared library is loaded. +Configure(config_txaFileFilter); +ConfigureFn(config_txaFileFilter) { + TxaFileFilter::init_type(); + TexturePool::register_filter(new TxaFileFilter); +} + +TypeHandle TxaFileFilter::_type_handle; +TxaFile *TxaFileFilter::_txa_file; +bool TxaFileFilter::_got_txa_file; + +/** + * This method is called after each texture has been loaded from disk, via the + * TexturePool, for the first time. By the time this method is called, the + * Texture has already been fully read from disk. This method should return + * the Texture pointer that the TexturePool should actually return (usually it + * is the same as the pointer supplied). + */ +PT(Texture) TxaFileFilter:: +post_load(Texture *tex) { + if (!_got_txa_file) { + read_txa_file(); + } + + TextureImage tex_image; + std::string name = tex->get_filename().get_basename_wo_extension(); + tex_image.set_name(name); + + SourceTextureImage *source = tex_image.get_source + (tex->get_fullpath(), tex->get_alpha_fullpath(), 0); + PNMImage pnm_image; + tex->store(pnm_image); + source->set_header(pnm_image); + tex_image.set_source_image(pnm_image); + + tex_image.pre_txa_file(); + + bool matched = _txa_file->match_texture(&tex_image); + if (txafile_cat.is_debug()) { + if (!matched) { + txafile_cat.debug() + << "Not matched: " << name << "\n"; + } else { + txafile_cat.debug() + << "Matched: " << name << "\n"; + } + } + + tex_image.post_txa_file(); + + PNMImage dest(tex_image.get_x_size(), + tex_image.get_y_size(), + tex_image.get_num_channels(), + pnm_image.get_maxval()); + dest.quick_filter_from(pnm_image); + + tex->load(dest); + + // Create an EggTexture to pass back the requested alpha mode to the egg + // loader, if the texture is now being loaded from an egg file. + PT_EggTexture egg_tex = new EggTexture(tex->get_name(), tex->get_fullpath()); + const TextureProperties &props = tex_image.get_properties(); + + egg_tex->set_alpha_mode(tex_image.get_alpha_mode()); + egg_tex->set_format(props._format); + egg_tex->set_minfilter(props._minfilter); + egg_tex->set_magfilter(props._magfilter); + egg_tex->set_anisotropic_degree(props._anisotropic_degree); + + tex->set_aux_data("egg", egg_tex); + + return tex; +} + +/** + * Reads the textures.txa file named by the variable txa-file. Called only + * once, at startup. + */ +void TxaFileFilter:: +read_txa_file() { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + // We need to create a global Palettizer object to hold some of the global + // properties that may be specified in a txa file. + if (pal == nullptr) { + pal = new Palettizer; + } + + _txa_file = new TxaFile; + _got_txa_file = true; + + ConfigVariableFilename txa_file + ("txa-file", Filename("textures.txa"), + PRC_DESC("Specify the name of the txa file to load when the txafile texture filter" + "is in effect.")); + + Filename filename = txa_file; + vfs->resolve_filename(filename, get_model_path()); + + if (!vfs->exists(filename)) { + txafile_cat.warning() + << "Filename " << filename << " not found.\n"; + } else { + filename.set_text(); + std::istream *ifile = vfs->open_read_file(filename, true); + if (ifile == nullptr) { + txafile_cat.warning() + << "Filename " << filename << " cannot be read.\n"; + } else { + if (!_txa_file->read(*ifile, filename)) { + txafile_cat.warning() + << "Syntax errors in " << filename << "\n"; + } else { + txafile_cat.info() + << "Read " << filename << "\n"; + } + vfs->close_read_file(ifile); + } + } +} diff --git a/pandatool/src/egg-palettize/txaFileFilter.h b/pandatool/src/egg-palettize/txaFileFilter.h new file mode 100644 index 00000000..a2f13a71 --- /dev/null +++ b/pandatool/src/egg-palettize/txaFileFilter.h @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaFileFilter.h + * @author drose + * @date 2006-07-27 + */ + +#ifndef TXAFILEFILTER_H +#define TXAFILEFILTER_H + +#include "pandatoolbase.h" +#include "texturePoolFilter.h" +#include "pt_EggTexture.h" + +class TxaFile; + +/** + * This is an abstract base class, a placeholder for any number of different + * classes that may wish to implement an effect on every texture loaded from + * disk via the TexturePool. + * + * In practice, as of the time of this writing, only the TxaFileFilter (in + * pandatool) actually implements this. But other kinds of filters are + * possible. + * + * This filter, once registered, will get a callback and a chance to modify + * each texture as it is loaded from disk the first time. If more than one + * filter is registered, each will be called in sequence, in the order in + * which they were registered. + * + * The filter does not get called again if the texture is subsequently + * reloaded from disk. It is suggested that filters for which this might be a + * problem should call tex->set_keep_ram_image(true). + */ +class EXPCL_MISC TxaFileFilter : public TexturePoolFilter { +public: + virtual PT(Texture) post_load(Texture *tex); + +private: + static void read_txa_file(); + +private: + static TxaFile *_txa_file; + static bool _got_txa_file; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TexturePoolFilter::init_type(); + register_type(_type_handle, "TxaFileFilter", + TexturePoolFilter::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "txaFileFilter.I" + +#endif diff --git a/pandatool/src/egg-qtess/CMakeLists.txt b/pandatool/src/egg-qtess/CMakeLists.txt new file mode 100644 index 00000000..83e6ff1f --- /dev/null +++ b/pandatool/src/egg-qtess/CMakeLists.txt @@ -0,0 +1,35 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG) + return() +endif() + +set(P3EGG_QTESS_HEADERS + config_egg_qtess.h + eggQtess.h + isoPlacer.h isoPlacer.I + qtessGlobals.h + qtessInputEntry.h qtessInputEntry.I + qtessInputFile.h qtessInputFile.I + qtessSurface.h qtessSurface.I + subdivSegment.h subdivSegment.I +) + +set(P3EGG_QTESS_SOURCES + config_egg_qtess.cxx + eggQtess.cxx + isoPlacer.cxx + qtessGlobals.cxx + qtessInputEntry.cxx + qtessInputFile.cxx + qtessSurface.cxx + subdivSegment.cxx +) + +composite_sources(egg-qtess P3EGG_QTESS_SOURCES) +add_executable(egg-qtess ${P3EGG_QTESS_HEADERS} ${P3EGG_QTESS_SOURCES}) +target_link_libraries(egg-qtess p3eggbase) + +install(TARGETS egg-qtess EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/egg-qtess/config_egg_qtess.cxx b/pandatool/src/egg-qtess/config_egg_qtess.cxx new file mode 100644 index 00000000..de3dbf95 --- /dev/null +++ b/pandatool/src/egg-qtess/config_egg_qtess.cxx @@ -0,0 +1,22 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_egg_qtess.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "config_egg_qtess.h" + +#include "dconfig.h" + +Configure(config_egg_qtess); +NotifyCategoryDef(qtess, ""); + +ConfigureFn(config_egg_qtess) { +} diff --git a/pandatool/src/egg-qtess/config_egg_qtess.h b/pandatool/src/egg-qtess/config_egg_qtess.h new file mode 100644 index 00000000..abc9ef39 --- /dev/null +++ b/pandatool/src/egg-qtess/config_egg_qtess.h @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_egg_qtess.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef CONFIG_EGG_QTESS_H +#define CONFIG_EGG_QTESS_H + +#include "pandatoolbase.h" +#include "notifyCategoryProxy.h" + +NotifyCategoryDeclNoExport(qtess); + +// No variables to declare here. + +#endif diff --git a/pandatool/src/egg-qtess/egg-qtess_composite1.cxx b/pandatool/src/egg-qtess/egg-qtess_composite1.cxx new file mode 100644 index 00000000..01ed99bd --- /dev/null +++ b/pandatool/src/egg-qtess/egg-qtess_composite1.cxx @@ -0,0 +1,8 @@ +#include "config_egg_qtess.cxx" +#include "eggQtess.cxx" +#include "isoPlacer.cxx" +#include "qtessGlobals.cxx" +#include "qtessInputEntry.cxx" +#include "qtessInputFile.cxx" +#include "qtessSurface.cxx" +#include "subdivSegment.cxx" diff --git a/pandatool/src/egg-qtess/eggQtess.cxx b/pandatool/src/egg-qtess/eggQtess.cxx new file mode 100644 index 00000000..37473557 --- /dev/null +++ b/pandatool/src/egg-qtess/eggQtess.cxx @@ -0,0 +1,337 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggQtess.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "eggQtess.h" +#include "qtessGlobals.h" +#include "dcast.h" + +/** + * + */ +EggQtess:: +EggQtess() { + add_normals_options(); + + set_program_brief("tesselate NURBS surfaces in .egg files"); + set_program_description + ("egg-qtess reads an egg file, tessellates all of its NURBS surfaces " + "using a simple uniform tessellation, and outputs a polygonal " + "egg file.\n\n" + + "Characters are supported, soft-skinned and otherwise; joint " + "ownership is computed correctly for each new polygon vertex. " + "Primitives other than NURBS surfaces appearing in the egg file " + "are unaffected."); + + add_option + ("f", "filename", 0, + "Read the indicated parameter file. Type egg-qtess -H " + "to print a description of the parameter file format.", + &EggQtess::dispatch_filename, nullptr, &_qtess_filename); + + add_option + ("up", "subdiv", 0, + "Specify a uniform subdivision per patch (isoparam). Each NURBS " + "surface is made up of N x M patches, each of which is divided " + "into subdiv x subdiv quads. A fractional number is allowed.", + &EggQtess::dispatch_double, nullptr, &_uniform_per_isoparam); + + add_option + ("us", "subdiv", 0, + "Specify a uniform subdivision per surface. Each NURBS " + "surface is subdivided into subdiv x subdiv quads, regardless " + "of the number of isoparams it has. A fractional number is " + "meaningless.", + &EggQtess::dispatch_int, nullptr, &_uniform_per_surface); + + add_option + ("t", "tris", 0, + "Specify an approximate number of triangles to produce. This " + "is the total number of triangles for the entire egg file, " + "including those surfaces that have already been given an " + "explicit tessellation by a parameter file.", + &EggQtess::dispatch_int, nullptr, &_total_tris); + + add_option + ("ap", "", 0, + "Attempt to automatically place tessellation lines where they'll " + "do the most good on each surface (once the number of polygons " + "for the surface has already been determined).", + &EggQtess::dispatch_none, &QtessGlobals::_auto_place); + + add_option + ("ad", "", 0, + "Attempt to automatically distribute polygons among the surfaces " + "where they are most needed according to curvature and size, " + "instead of according to the number of isoparams. This only has " + "meaning when used in conjunction with -t.", + &EggQtess::dispatch_none, &QtessGlobals::_auto_distribute); + + add_option + ("ar", "ratio", 0, + "Specify the ratio of dominance of size to curvature for -ap and " + "-ad. A value of 0 forces placement by curvature only; a very " + "large value (like 1000) forces placement by size only. The " + "default is 5.0.", + &EggQtess::dispatch_double, nullptr, &QtessGlobals::_curvature_ratio); + + add_option + ("e", "", 0, + "Respect subdivision parameters given in the egg file. If this " + "is specified, the egg file may define the effective number of " + "patches of each NURBS entry. This can be used alone or in " + "conjunction with -u or -t to fine-tune the uniform tessellation " + "on a per-surface basis. (This is ignored if -ad is in effect.)", + &EggQtess::dispatch_none, &QtessGlobals::_respect_egg); + + add_option + ("q", "", 0, + "Instead of writing an egg file, generate a parameter file " + "for output.", + &EggQtess::dispatch_none, &_qtess_output); + + add_option + ("H", "", 0, + "Describe the format of the parameter file specified with -f.", + &EggQtess::dispatch_none, &_describe_qtess); + + _uniform_per_isoparam = 0.0; + _uniform_per_surface = 0; + _total_tris = 0; +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggQtess:: +handle_args(ProgramBase::Args &args) { + if (_describe_qtess) { + describe_qtess_format(); + exit(0); + } + + return EggFilter::handle_args(args); +} + +/** + * + */ +void EggQtess:: +run() { + bool read_qtess = false; + if (!_qtess_filename.empty()) { + if (!_qtess_file.read(_qtess_filename)) { + exit(1); + } + read_qtess = true; + } + + find_surfaces(_data); + + QtessInputEntry &default_entry = _qtess_file.get_default_entry(); + if (!read_qtess || default_entry.get_num_surfaces() == 0) { + nout << _surfaces.size() << " NURBS surfaces found.\n"; + + } else { + nout << _surfaces.size() << " NURBS surfaces found; " + << default_entry.get_num_surfaces() + << " unaccounted for by input file.\n"; + } + + int num_tris = _qtess_file.count_tris(); + + if (_total_tris != 0) { + // Whatever number of triangles we have unaccounted for, assign to the + // default bucket. + int extra_tris = std::max(0, _total_tris - num_tris); + if (read_qtess && default_entry.get_num_surfaces() != 0) { + std::cerr << extra_tris << " triangles unaccounted for.\n"; + } + + default_entry.set_num_tris(extra_tris); + + } else if (_uniform_per_isoparam!=0.0) { + default_entry.set_per_isoparam(_uniform_per_isoparam); + + } else if (_uniform_per_surface!=0.0) { + default_entry.set_uv(_uniform_per_surface, _uniform_per_surface); + + } else { + default_entry.set_per_isoparam(1.0); + } + + default_entry.count_tris(); + + if (_qtess_output) { + // Sort the names into alphabetical order for aesthetics. + // sort(_surfaces.begin(), _surfaces.end(), compare_surfaces()); + + int tris = 0; + + std::ostream &out = get_output(); + Surfaces::const_iterator si; + for (si = _surfaces.begin(); si != _surfaces.end(); ++si) { + tris += (*si)->write_qtess_parameter(out); + } + + std::cerr << tris << " tris generated.\n"; + + } else { + + int tris = 0; + + Surfaces::const_iterator si; + for (si = _surfaces.begin(); si != _surfaces.end(); ++si) { + tris += (*si)->tesselate(); + } + + std::cerr << tris << " tris generated.\n"; + + // Clear out the surfaces list before removing the vertices, since each + // surface is holding reference counts to the previously-used vertices. + _surfaces.clear(); + + _data->remove_unused_vertices(true); + write_egg_file(); + } +} + +/** + * + */ +void EggQtess:: +describe_qtess_format() { + nout << + "An egg-qtess parameter file consists of lines of the form:\n\n" + + "name [name...] : parameters\n\n" + + "Where name is a string (possibly including wildcard characters " + "such as * and ?) that matches one or more surface " + "names, and parameters is a tesselation specification, described below. " + "The colon must be followed by at least one space to differentiate it " + "from a colon character in the name(s). Multiple names " + "may be combined on one line.\n\n\n" + + + "The parameters may be any of the following. Lowercase letters are " + "literal. NUM is any number.\n\n"; + + show_text(" omit", 10, + "Remove the surface from the output.\n\n"); + + show_text(" NUM", 10, + "Try to achieve the indicated number of triangles over all the " + "surfaces matched by this line.\n\n"); + + show_text(" NUM NUM [[!]u# [!]u# ...] [[!]v# [!]v# ...]", 10, + "Tesselate to NUM x NUM quads. If u# or v# appear, they indicate " + "additional isoparams to insert (or remove if preceded by an " + "exclamation point). The range is [0, 1].\n\n"); + + show_text(" iNUM", 10, + "Subdivision amount per isoparam. Equivalent to the command-line " + "option -u NUM.\n\n"); + + show_text(" NUM%", 10, + "This is a special parameter. This does not request any specific " + "tesselation for the named surfaces, but instead gives a relative " + "importance for them when they appear with other surfaces in a " + "later entry (or are tesselated via -t on the command line). In " + "general, a surface with a weight of 25% will be given a quarter " + "of the share of the polygons it otherwise would have received; " + "a weight of 150% will give the surface 50% more than its fair " + "share.\n\n"); + + show_text(" matchvu", 10, + "This is a special parameter that indicates that two or more " + "surfaces share a common edge, and must be tesselated the " + "same way " + "along that edge. Specifically, matchvu means that the V " + "tesselation of the first named surface will be applied to the U " + "tesselation of the second (and later) named surface(s). Similar " + "definitions exist for matchuv, matchuu, and matchvv.\n\n"); + + show_text(" minu NUM", 10, + "This is another special parameter that specifies a " + "minimum tesselation for all these surfaces in " + "the U direction. This is " + "the number of quads across the dimension the surface will be " + "broken into. The default is 1 for an open surface, and 3 for " + "a closed surface.\n\n"); + + show_text(" minv NUM", 10, + "Similar to minv, in the V direction.\n\n"); + + nout << + "In addition, the following optional parameters may appear. If they appear, " + "they override similar parameters given on the command line; if they do not " + "appear, the defaults are taken from the command line:\n\n"; + + show_text(" ap", 10, + "Automatically place tesselation lines on each surface where they " + "seem to be needed most.\n\n"); + + show_text(" !ap", 10, + "Do not move lines automatically; use a strict uniform " + "tesselation.\n\n"); + + show_text(" ad", 10, + "Automatically distribute polygons to the surfaces that seem to " + "need them the most.\n\n"); + + show_text(" !ad", 10, + "Do not automatically distribute polygons; distribute " + "them according to the number of isoparams of each surface.\n\n"); + + show_text(" arNUM", 10, + "Specify the ratio of dominance of size to curvature.\n\n"); + + nout << + "The hash symbol '#' begins a comment if it is preceded by whitespace or at the " + "beginning of a line. The backslash character at the end of a line can be used " + "to indicate a continuation.\n\n"; +} + +/** + * Recursively walks the egg graph, collecting all the NURBS surfaces found. + */ +void EggQtess:: +find_surfaces(EggNode *egg_node) { + if (egg_node->is_of_type(EggNurbsSurface::get_class_type())) { + PT(QtessSurface) surface = + new QtessSurface(DCAST(EggNurbsSurface, egg_node)); + if (surface->is_valid()) { + _surfaces.push_back(surface); + QtessInputEntry::Type match_type = _qtess_file.match(surface); + nassertv(match_type != QtessInputEntry::T_undefined); + } + } + + if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); + EggGroupNode::const_iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + find_surfaces(*ci); + } + } +} + +int main(int argc, char *argv[]) { + EggQtess prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/egg-qtess/eggQtess.h b/pandatool/src/egg-qtess/eggQtess.h new file mode 100644 index 00000000..c18db52e --- /dev/null +++ b/pandatool/src/egg-qtess/eggQtess.h @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggQtess.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef EGGQTESS_H +#define EGGQTESS_H + +#include "pandatoolbase.h" +#include "eggFilter.h" +#include "qtessInputFile.h" +#include "qtessSurface.h" +#include "pointerTo.h" +#include "pvector.h" + +/** + * A program to tesselate NURBS surfaces appearing within an egg file into + * polygons, using variations on a quick uniform tesselation. + */ +class EggQtess : public EggFilter { +public: + EggQtess(); + + void run(); + +protected: + virtual bool handle_args(ProgramBase::Args &args); + +private: + void describe_qtess_format(); + void find_surfaces(EggNode *egg_node); + + Filename _qtess_filename; + double _uniform_per_isoparam; + int _uniform_per_surface; + int _total_tris; + bool _qtess_output; + bool _describe_qtess; + + QtessInputFile _qtess_file; + + typedef pvector< PT(QtessSurface) > Surfaces; + Surfaces _surfaces; +}; + +#endif diff --git a/pandatool/src/egg-qtess/isoPlacer.I b/pandatool/src/egg-qtess/isoPlacer.I new file mode 100644 index 00000000..f773b8fb --- /dev/null +++ b/pandatool/src/egg-qtess/isoPlacer.I @@ -0,0 +1,28 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file isoPlacer.I + * @author drose + * @date 2003-10-13 + */ + +/** + * + */ +INLINE IsoPlacer:: +IsoPlacer() { +} + + +/** + * + */ +INLINE double IsoPlacer:: +get_total_score() const { + return _cint[_maxi]; +} diff --git a/pandatool/src/egg-qtess/isoPlacer.cxx b/pandatool/src/egg-qtess/isoPlacer.cxx new file mode 100644 index 00000000..0f7a6a3f --- /dev/null +++ b/pandatool/src/egg-qtess/isoPlacer.cxx @@ -0,0 +1,228 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file isoPlacer.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "isoPlacer.h" +#include "qtessSurface.h" +#include "subdivSegment.h" +#include "nurbsSurfaceResult.h" +#include "pvector.h" + + +/** + * + */ +void IsoPlacer:: +get_scores(int subdiv, int across, double ratio, + NurbsSurfaceResult *surf, bool s) { + _maxi = subdiv - 1; + + _cscore.clear(); + _sscore.clear(); + + _cscore.reserve(_maxi); + _sscore.reserve(_maxi); + + // First, tally up the curvature and stretch scores across the surface. + int i = 0; + for (i = 0; i < _maxi; i++) { + _cscore.push_back(0.0); + _sscore.push_back(0.0); + } + + int a; + for (a = 0; a <= across; a++) { + double v = (double)a / (double)across; + + LVecBase3 p1, p2, p3, pnext; + LVecBase3 v1, v2; + if (s) { + surf->eval_point(0.0, v, p3); + } else { + surf->eval_point(v, 0.0, p3); + } + int num_points = 1; + + for (i = -1; i < _maxi; i++) { + double u = (double)(i+1) / (double)(_maxi+1); + if (s) { + surf->eval_point(u, v, pnext); + } else { + surf->eval_point(v, u, pnext); + } + + // We'll ignore consecutive equal points. They don't contribute to + // curvature or size. + if (!pnext.almost_equal(p3)) { + num_points++; + p1 = p2; + p2 = p3; + p3 = pnext; + + v1 = v2; + v2 = p3 - p2; + double vlength = length(v2); + v2 /= vlength; + + if (i >= 0) { + _sscore[i] += vlength; + } + + if (num_points >= 3) { + // We only have a meaningful v1, v2 when we've read at least three + // non-equal points. + double d = v1.dot(v2); + + _cscore[i] += acos(std::max(std::min(d, 1.0), -1.0)); + } + } + } + } + + // Now integrate. + _cint.clear(); + _cint.reserve(_maxi + 1); + + double net = 0.0; + double ad = (double)(across+1); + _cint.push_back(0.0); + for (i = 0; i < _maxi; i++) { + net += _cscore[i]/ad + ratio * _sscore[i]/ad; + _cint.push_back(net); + } +} + +/** + * + */ +void IsoPlacer:: +place(int count, pvector &iso_points) { + // Count up the average curvature. + /* + double avg_curve = 0.0; + for (i = 0; i < _maxi; i++) { + avg_curve += _cscore[i]; + } + avg_curve /= (double)_maxi; + */ + + // Find all the local maxima in the curvature table. These are bend points. + typedef pvector BendPoints; + BendPoints bpoints; + BendPoints::iterator bi, bnext; + + typedef pvector Segments; + Segments segments; + Segments::iterator si; + + /* + // Having problems with bend points right now. Maybe this is just a bad + // idea. It seems to work pretty well without them, anyway. + for (i = 1; i < _maxi-1; i++) { + // A point must be measurably higher than both its neighbors, as well as + // at least 50% more curvy than the average curvature, to qualify as a + // bend point. + if (_cscore[i] > _cscore[i-1]+0.001 && + _cscore[i] > _cscore[i+1]+0.001 && + _cscore[i] > 1.5 * avg_curve) { + bpoints.push_back(i); + } + } + */ + + // Now make sure there aren't any two bend points closer together than + // maxicount. If there are, remove the smaller of the two. + bi = bpoints.begin(); + int min_separation = _maxi/count; + while (bi != bpoints.end()) { + bnext = bi; + ++bnext; + + if (bnext != bpoints.end() && (*bnext) - (*bi) < min_separation) { + // Too close. Remove one. + if (_cscore[*bnext] > _cscore[*bi]) { + *bi = *bnext; + } + bpoints.erase(bnext); + } else { + // Not too close; keep going; + bi = bnext; + } + } + + // Now, if we have fewer total subdivisions than bend points, then remove + // the smallest bend points. + while (count - 1 < (int)bpoints.size()) { + bi = bpoints.begin(); + BendPoints::iterator mi = bi; + for (++bi; bi != bpoints.end(); ++bi) { + if (_cscore[*bi] < _cscore[*mi]) { + mi = bi; + } + } + bpoints.erase(mi); + } + + // Now all the remaining bend points are valid. + bi = bpoints.begin(); + int last = 0; + for (bi = bpoints.begin(); bi != bpoints.end(); ++bi) { + segments.push_back(SubdivSegment(&_cint[0], last, *bi)); + last = *bi; + } + segments.push_back(SubdivSegment(&_cint[0], last, _maxi)); + + int nr = count - segments.size(); + + // Now we have subdivided the curve into a number of smaller curves at the + // bend points. We still have nr remaining cuts to make; distribute these + // cuts among the curves evenly according to score. + + // Divvy out the extra cuts. First, each segment gets an amount + // proportional to its score. + double net_score = _cint[_maxi]; + nassertv(net_score > 0.0); + int ns = 0; + for (si = segments.begin(); si != segments.end(); ++si) { + (*si)._num_cuts = (int)floor(nr * (*si).get_score() / net_score); + nassertv((*si)._num_cuts <= nr); // This fails if net_score is nan. + ns += (*si)._num_cuts; + } + + // Then, assign the remaining cuts to the neediest segments. + nr -= ns; + while (nr > 0) { + si = min_element(segments.begin(), segments.end()); + (*si)._num_cuts++; + nr--; + } + + // Now cut up the segments as indicated. + for (si = segments.begin(); si != segments.end(); ++si) { + (*si).cut(); + } + + // Finally, return the result. + iso_points.erase(iso_points.begin(), iso_points.end()); + + iso_points.push_back(0.0); + for (si = segments.begin(); si != segments.end(); ++si) { + pvector::iterator ci; + for (ci = (*si)._cuts.begin(); ci != (*si)._cuts.end(); ++ci) { + iso_points.push_back((*ci+1) / (double)(_maxi+1)); + } + iso_points.push_back(((*si)._t+1) / (double)(_maxi+1)); + } + + // Oh, wait. The last segment is actually drawn all the way to 1. + iso_points.back() = 1.0; +} diff --git a/pandatool/src/egg-qtess/isoPlacer.h b/pandatool/src/egg-qtess/isoPlacer.h new file mode 100644 index 00000000..b4696340 --- /dev/null +++ b/pandatool/src/egg-qtess/isoPlacer.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file isoPlacer.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef ISOPLACER_H +#define ISOPLACER_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "vector_double.h" + +class NurbsSurfaceResult; + +/** + * Contains the logic used to place isoparams where they'll do the most good + * on a surface. + */ +class IsoPlacer { +public: + INLINE IsoPlacer(); + + void get_scores(int subdiv, int across, double ratio, + NurbsSurfaceResult *surf, bool s); + void place(int count, pvector &iso_points); + + INLINE double get_total_score() const; + + vector_double _cscore, _sscore, _cint; + int _maxi; +}; + +#include "isoPlacer.I" + +#endif diff --git a/pandatool/src/egg-qtess/qtessGlobals.cxx b/pandatool/src/egg-qtess/qtessGlobals.cxx new file mode 100644 index 00000000..85fb91fb --- /dev/null +++ b/pandatool/src/egg-qtess/qtessGlobals.cxx @@ -0,0 +1,19 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessGlobals.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "qtessGlobals.h" + +bool QtessGlobals::_auto_place = false; +bool QtessGlobals::_auto_distribute = false; +double QtessGlobals::_curvature_ratio = 5.0; +bool QtessGlobals::_respect_egg = false; diff --git a/pandatool/src/egg-qtess/qtessGlobals.h b/pandatool/src/egg-qtess/qtessGlobals.h new file mode 100644 index 00000000..d7412934 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessGlobals.h @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessGlobals.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef QTESS_GLOBALS_H +#define QTESS_GLOBALS_H + +#include "pandatoolbase.h" + +/** + * Simply used as a namespace to scope some global variables for this program, + * set from the command line. + */ +class QtessGlobals { +public: + static bool _auto_place; + static bool _auto_distribute; + static double _curvature_ratio; + static bool _respect_egg; +}; + +#endif diff --git a/pandatool/src/egg-qtess/qtessInputEntry.I b/pandatool/src/egg-qtess/qtessInputEntry.I new file mode 100644 index 00000000..d8ab6de6 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputEntry.I @@ -0,0 +1,156 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputEntry.I + * @author drose + * @date 2003-10-13 + */ + +/** + * + */ +INLINE QtessInputEntry:: +QtessInputEntry(const QtessInputEntry ©) { + (*this) = copy; +} + +/** + * + */ +INLINE void QtessInputEntry:: +add_node_name(const std::string &name) { + _node_names.push_back(GlobPattern(name)); +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_importance(double i) { + _importance = i; + _type = T_importance; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_match_uu() { + _type = T_match_uu; + _constrain_u = nullptr; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_match_vv() { + _type = T_match_vv; + _constrain_v = nullptr; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_match_uv() { + _type = T_match_uv; + _constrain_u = nullptr; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_match_vu() { + _type = T_match_vu; + _constrain_v = nullptr; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_min_u(int min_u) { + _type = T_min_u; + _num_u = min_u; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_min_v(int min_v) { + _type = T_min_v; + _num_v = min_v; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_undefined() { + _type = T_undefined; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_omit() { + _type = T_omit; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_num_tris(int nt) { + _num_tris = nt; + _type = T_num_tris; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_uv(int u, int v) { + set_uv(u, v, nullptr, 0); +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_per_isoparam(double pi) { + _per_isoparam = pi; + _type = T_per_isoparam; +} + +/** + * + */ +INLINE void QtessInputEntry:: +set_per_score(double pi) { + _per_isoparam = pi; + _type = T_per_score; +} + +/** + * + */ +INLINE int QtessInputEntry:: +get_num_surfaces() const { + return _surfaces.size(); +} + + +INLINE std::ostream &operator << (std::ostream &out, const QtessInputEntry &entry) { + entry.output(out); + return out; +} diff --git a/pandatool/src/egg-qtess/qtessInputEntry.cxx b/pandatool/src/egg-qtess/qtessInputEntry.cxx new file mode 100644 index 00000000..66df4c88 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputEntry.cxx @@ -0,0 +1,465 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputEntry.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "qtessInputEntry.h" +#include "qtessSurface.h" +#include "qtessGlobals.h" +#include "config_egg_qtess.h" +#include "indent.h" +#include "string_utils.h" + +#include +#include + +using std::string; + +/** + * + */ +QtessInputEntry:: +QtessInputEntry(const string &name) { + _type = T_undefined; + _num_patches = 0.0; + _auto_place = QtessGlobals::_auto_place; + _auto_distribute = QtessGlobals::_auto_distribute; + _curvature_ratio = QtessGlobals::_curvature_ratio; + if (!name.empty()) { + add_node_name(name); + } +} + +/** + * + */ +void QtessInputEntry:: +operator = (const QtessInputEntry ©) { + _node_names = copy._node_names; + _type = copy._type; + _num_tris = copy._num_tris; + _num_u = copy._num_u; + _num_v = copy._num_v; + _per_isoparam = copy._per_isoparam; + _iso_u = copy._iso_u; + _iso_v = copy._iso_v; + _surfaces = copy._surfaces; + _num_patches = copy._num_patches; + _auto_place = copy._auto_place; + _auto_distribute = copy._auto_distribute; + _curvature_ratio = copy._curvature_ratio; + _importance = copy._importance; + _constrain_u = copy._constrain_u; + _constrain_v = copy._constrain_v; +} + +/** + * An STL function object to determine if two doubles are very nearly equal. + * Used in set_uv(), below. + */ +class DoublesAlmostEqual { +public: + int operator ()(double a, double b) const { + return fabs(a - b) < 0.00001; + } +}; + +/** + * An STL function object to determine if a double is vert nearly equal the + * supplied value . Used in set_uv(), below. + */ +class DoubleAlmostMatches { +public: + DoubleAlmostMatches(double v) : _v(v) {} + int operator ()(double a) const { + return fabs(a - _v) < 0.00001; + } + double _v; +}; + + +/** + * Sets specific tesselation. The tesselation will be u by v quads, with the + * addition of any isoparams described in the list of params. + */ +void QtessInputEntry:: +set_uv(int u, int v, const string params[], int num_params) { + _num_u = u; + _num_v = v; + + // First, fill up the arrays with the defaults. + int i; + for (i = 0; i <= _num_u; i++) { + _iso_u.push_back(i); + } + for (i = 0; i <= _num_v; i++) { + _iso_v.push_back(i); + } + + // Then get out all the additional entries. + for (i = 0; i < num_params; i++) { + const string ¶m = params[i]; + + if (param[0] == '!' && param.size() > 2) { + double value; + if (!string_to_double(param.substr(2), value)) { + qtess_cat.warning() + << "Ignoring invalid parameter: " << param << "\n"; + } else { + switch (tolower(param[1])) { + case 'u': + _auto_place = false; + _iso_u.erase(remove_if(_iso_u.begin(), _iso_u.end(), + DoubleAlmostMatches(value)), + _iso_u.end()); + break; + + case 'v': + _auto_place = false; + _iso_v.erase(remove_if(_iso_v.begin(), _iso_v.end(), + DoubleAlmostMatches(value)), + _iso_v.end()); + break; + + default: + qtess_cat.warning() + << "Ignoring invalid parameter: " << params[i] << "\n"; + } + } + } else { + double value; + if (!string_to_double(param.substr(1), value)) { + qtess_cat.warning() + << "Ignoring invalid parameter: " << param << "\n"; + } else { + switch (tolower(param[0])) { + case 'u': + _auto_place = false; + _iso_u.push_back(value); + break; + + case 'v': + _auto_place = false; + _iso_v.push_back(value); + break; + + default: + qtess_cat.warning() + << "Ignoring invalid parameter: " << params[i] << "\n"; + } + } + } + } + + // Now sort them into ascending order and remove duplicates. + sort(_iso_u.begin(), _iso_u.end()); + sort(_iso_v.begin(), _iso_v.end()); + _iso_u.erase(unique(_iso_u.begin(), _iso_u.end(), DoublesAlmostEqual()), _iso_u.end()); + _iso_v.erase(unique(_iso_v.begin(), _iso_v.end(), DoublesAlmostEqual()), _iso_v.end()); + + _type = T_uv; +} + + +/** + * May be called a number of times before set_uv() to add specific additional + * isoparams to the tesselation. + */ +void QtessInputEntry:: +add_extra_u_isoparam(double u) { + _iso_u.push_back(u); +} + +/** + * May be called a number of times before set_uv() to add specific additional + * isoparams to the tesselation. + */ +void QtessInputEntry:: +add_extra_v_isoparam(double v) { + _iso_v.push_back(v); +} + +/** + * Tests the surface to see if it matches any of the regular expressions that + * define this node entry. If so, adds it to the set of matched surfaces and + * returns the type of the matching entry. If no match is found, returns + * T_undefined. + */ +QtessInputEntry::Type QtessInputEntry:: +match(QtessSurface *surface) { + const string &name = surface->get_name(); + + NodeNames::const_iterator nni; + for (nni = _node_names.begin(); + nni != _node_names.end(); + ++nni) { + const GlobPattern &pattern = (*nni); + if (pattern.matches(name)) { + // We have a winner! + switch (_type) { + case T_importance: + // A type of "Importance" is a special case. This entry doesn't + // specify any kind of tesselation on the surface, and in fact doesn't + // preclude the surface from matching anything later. It just + // specifies the relative importance of the surface to all the other + // surfaces. + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << "Assigning importance of " << _importance*100.0 + << "% to " << name << "\n"; + } + surface->set_importance(_importance); + return T_undefined; + + case T_match_uu: + case T_match_uv: + // Similarly for type "matchUU". This indicates that all the surfaces + // that match this one must all share the U-tesselation with whichever + // surface first matched against the first node name. + if (nni == _node_names.begin() && _constrain_u==nullptr) { + // This is the lucky surface that dominates! + _constrain_u = surface; + } else { + if (_type == T_match_uu) { + surface->set_match_u(&_constrain_u, true); + } else { + surface->set_match_v(&_constrain_u, false); + } + } + return T_undefined; + + case T_match_vv: + case T_match_vu: + // Ditto for "matchVV". + if (nni == _node_names.begin() && _constrain_v==nullptr) { + // This is the lucky surface that dominates! + _constrain_v = surface; + } else { + if (_type == T_match_vv) { + surface->set_match_v(&_constrain_v, true); + } else { + surface->set_match_u(&_constrain_v, false); + } + } + return T_undefined; + + case T_min_u: + // And for min U and V. + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << "Assigning minimum of " << _num_u << " in U to " + << name << "\n"; + } + surface->set_min_u(_num_u); + return T_undefined; + + case T_min_v: + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << "Assigning minimum of " << _num_v << " in V to " + << name << "\n"; + } + surface->set_min_v(_num_v); + return T_undefined; + + default: + _surfaces.push_back(surface); + if (_auto_distribute) { + _num_patches += surface->get_score(_curvature_ratio); + } else { + _num_patches += surface->count_patches(); + } + return _type; + } + } + } + + return T_undefined; +} + +/** + * Determines the tesselation u,v amounts of each attached surface, and stores + * this information in the surface pointer. Returns the total number of tris + * that will be produced. + */ +int QtessInputEntry:: +count_tris(double tri_factor, int attempts) { + int total_tris = 0; + bool aim_for_tris = false; + + if (_type == T_num_tris && _num_patches > 0.0) { + // If we wanted to aim for a particular number of triangles for the group, + // choose a per-isoparam setting that will approximately achieve this. + if (_auto_distribute) { + set_per_score(sqrt(0.5 * (double)_num_tris / _num_patches / tri_factor)); + } else { + set_per_isoparam(sqrt(0.5 * (double)_num_tris / _num_patches / tri_factor)); + } + aim_for_tris = true; + } + + Surfaces::iterator si; + for (si = _surfaces.begin(); si != _surfaces.end(); ++si) { + QtessSurface *surface = (*si); + + switch (_type) { + case T_undefined: + case T_omit: + surface->omit(); + break; + + case T_uv: + if (!_iso_u.empty() && !_iso_v.empty() && !_auto_place) { + surface->tesselate_specific(_iso_u, _iso_v); + } else { + surface->tesselate_uv(_num_u, _num_v, _auto_place, _curvature_ratio); + } + break; + + case T_per_isoparam: + surface->tesselate_per_isoparam(_per_isoparam, _auto_place, _curvature_ratio); + break; + + case T_per_score: + surface->tesselate_per_score(_per_isoparam, _auto_place, _curvature_ratio); + break; + + default: + break; + } + + total_tris += surface->count_tris(); + } + + if (aim_for_tris && attempts < 10 && + (double)total_tris / (double)_num_tris > 1.1) { + // We'd like to get within 10% of the requested number of triangles, if + // possible. Keep trying until we do, or until we just need to give up. + set_num_tris(_num_tris); + return count_tris(tri_factor * total_tris / _num_tris, attempts + 1); + } + + return total_tris; +} + + +/** + * This function is used to identify the extra isoparams in the list added by + * user control. + */ +void QtessInputEntry:: +output_extra(std::ostream &out, const pvector &iso, char axis) { + pvector::const_iterator di; + int expect = 0; + for (di = iso.begin(); di != iso.end(); ++di) { + while ((*di) > (double)expect) { + // Didn't find one we were expecting. Omit it. + out << " !" << axis << expect; + } + if ((*di)==(double)expect) { + // Here's one we were expecting; ignore it. + expect++; + } else { + // Here's a new one. Write it. + out << " " << axis << *di; + } + } +} + +/** + * + */ +void QtessInputEntry:: +output(std::ostream &out) const { + NodeNames::const_iterator nni; + for (nni = _node_names.begin(); + nni != _node_names.end(); + ++nni) { + out << (*nni) << " "; + } + out << ": "; + + bool show_auto = false; + + switch (_type) { + case T_undefined: + break; + + case T_omit: + out << "omit"; + break; + + case T_num_tris: + out << _num_tris; + show_auto = true; + break; + + case T_uv: + out << _num_u << " " << _num_v; + output_extra(out, _iso_u, 'u'); + output_extra(out, _iso_v, 'v'); + show_auto = true; + break; + + case T_per_isoparam: + case T_per_score: + out << "i" << _per_isoparam; + show_auto = true; + break; + + case T_importance: + out << _importance * 100.0 << "%"; + break; + + case T_match_uu: + out << "matchuu"; + break; + + case T_match_vv: + out << "matchvv"; + break; + + case T_match_uv: + out << "matchuv"; + break; + + case T_match_vu: + out << "matchvu"; + break; + + case T_min_u: + out << "minu " << _num_u; + break; + + case T_min_v: + out << "minv " << _num_v; + break; + + default: + out << "Invalid!"; + } + + if (show_auto) { + out << " " << (_auto_place?"":"!") << "ap" + << " " << (_auto_distribute?"":"!") << "ad"; + if (_auto_place || _auto_distribute) { + out << " ar" << _curvature_ratio; + } + } +} + +/** + * + */ +void QtessInputEntry:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) << (*this) << "\n"; +} diff --git a/pandatool/src/egg-qtess/qtessInputEntry.h b/pandatool/src/egg-qtess/qtessInputEntry.h new file mode 100644 index 00000000..bffcca0d --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputEntry.h @@ -0,0 +1,90 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputEntry.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef QTESSINPUTENTRY_H +#define QTESSINPUTENTRY_H + +#include "pandatoolbase.h" +#include "globPattern.h" +#include "pvector.h" + +class QtessSurface; + +/** + * Stores one entry in the qtess input file. This consists of a list of name + * patterns and a set of tesselation parameters. + */ +class QtessInputEntry { +public: + enum Type { + T_undefined, T_omit, T_num_tris, T_uv, T_per_isoparam, T_per_score, + T_importance, T_match_uu, T_match_vv, T_match_uv, T_match_vu, + T_min_u, T_min_v + }; + + QtessInputEntry(const std::string &name = std::string()); + INLINE QtessInputEntry(const QtessInputEntry ©); + void operator = (const QtessInputEntry ©); + + INLINE void add_node_name(const std::string &name); + INLINE void set_importance(double i); + INLINE void set_match_uu(); + INLINE void set_match_vv(); + INLINE void set_match_uv(); + INLINE void set_match_vu(); + INLINE void set_min_u(int min_u); + INLINE void set_min_v(int min_v); + INLINE void set_undefined(); + INLINE void set_omit(); + INLINE void set_num_tris(int nt); + INLINE void set_uv(int u, int v); + void set_uv(int u, int v, const std::string params[], int num_params); + INLINE void set_per_isoparam(double pi); + INLINE void set_per_score(double pi); + void add_extra_u_isoparam(double u); + void add_extra_v_isoparam(double u); + + Type match(QtessSurface *surface); + INLINE int get_num_surfaces() const; + int count_tris(double tri_factor = 1.0, int attempts = 0); + + static void output_extra(std::ostream &out, const pvector &iso, char axis); + void output(std::ostream &out) const; + void write(std::ostream &out, int indent_level) const; + + bool _auto_place, _auto_distribute; + double _curvature_ratio; + double _importance; + QtessSurface *_constrain_u, *_constrain_v; + +private: + typedef pvector NodeNames; + NodeNames _node_names; + + int _num_tris; + int _num_u, _num_v; + double _per_isoparam; + pvector _iso_u, _iso_v; + Type _type; + + typedef pvector Surfaces; + Surfaces _surfaces; + + double _num_patches; +}; + +INLINE std::ostream &operator << (std::ostream &out, const QtessInputEntry &entry); + +#include "qtessInputEntry.I" + +#endif diff --git a/pandatool/src/egg-qtess/qtessInputFile.I b/pandatool/src/egg-qtess/qtessInputFile.I new file mode 100644 index 00000000..313b59c8 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputFile.I @@ -0,0 +1,29 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputFile.I + * @author drose + * @date 2003-10-13 + */ + +/** + * + */ +INLINE QtessInputFile:: +QtessInputFile(const QtessInputFile ©) : + _entries(copy._entries) +{ +} + +/** + * + */ +INLINE void QtessInputFile:: +operator = (const QtessInputFile ©) { + _entries = copy._entries; +} diff --git a/pandatool/src/egg-qtess/qtessInputFile.cxx b/pandatool/src/egg-qtess/qtessInputFile.cxx new file mode 100644 index 00000000..180a2e95 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputFile.cxx @@ -0,0 +1,327 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputFile.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "qtessInputFile.h" +#include "config_egg_qtess.h" +#include "string_utils.h" + +using std::string; + +/** + * + */ +QtessInputFile:: +QtessInputFile() { +} + +/** + * reads the input file. + */ +bool QtessInputFile:: +read(const Filename &filename) { + _filename = Filename::text_filename(filename); + _entries.clear(); + + std::ifstream input; + if (!_filename.open_read(input)) { + qtess_cat.error() + << "Unable to open input file " << _filename << "\n"; + return false; + } + + string complete_line; + + int line_number = 0; + string line; + while (std::getline(input, line)) { + line_number++; + + // Eliminate comments. We have to scan the line repeatedly until we find + // the first hash mark that's preceded by whitespace. + size_t comment = line.find('#'); + while (comment != string::npos) { + if (comment == 0 || isspace(line[comment - 1])) { + line = line.substr(0, comment); + comment = string::npos; + + } else { + comment = line.find('#', comment + 1); + } + } + + // Check for a trailing backslash: continuation character. + line = trim_right(line); + if (!line.empty() && line[line.size() - 1] == '\\') { + // We have a continuation character; go back and read some more. + complete_line += line.substr(0, line.size() - 1); + + } else { + // It's a complete line. Begin parsing. + line = trim(complete_line + line); + complete_line = string(); + + if (!line.empty()) { + QtessInputEntry entry; + + // Scan for the first colon followed by whitespace. + size_t colon = line.find(": "); + if (colon == string::npos) { + qtess_cat.error() + << _filename << ": line " << line_number + << " has no colon followed by whitespace.\n"; + return false; + } + if (colon == 0) { + qtess_cat.error() + << _filename << ": line " << line_number + << " has no nodes.\n"; + return false; + } + + // Split the line into two groups of words at the colon: names before + // the colon, and params following it. + vector_string names, params; + extract_words(line.substr(0, colon), names); + extract_words(line.substr(colon + 1), params); + + vector_string::const_iterator ni; + for (ni = names.begin(); ni != names.end(); ++ni) { + entry.add_node_name(*ni); + } + + // Scan for things like ap, ad, ar, and pull them out of the stream. + vector_string::iterator ci, cnext; + ci = params.begin(); + while (ci != params.end()) { + cnext = ci; + ++cnext; + + string param = *ci; + bool invert = false; + if (param[0] == '!' && param.size() > 1) { + invert = true; + param = param.substr(1); + } + if (tolower(param[0]) == 'a' && param.size() > 1) { + switch (tolower(param[1])) { + case 'p': + entry._auto_place = !invert; + break; + + case 'd': + entry._auto_distribute = !invert; + break; + + case 'r': + if (!string_to_double(param.substr(2), entry._curvature_ratio)) { + qtess_cat.error() + << _filename << ": line " << line_number + << " - invalid field " << param << "\n"; + return false; + } + break; + + default: + qtess_cat.error() + << _filename << ": invalid parameters at line " + << line_number << ".\n"; + return false; + } + params.erase(ci); + } else { + ci = cnext; + } + } + + if (!params.empty()) { + bool okflag = true; + if (cmp_nocase(params[0], "omit")==0) { + entry.set_omit(); + + } else if (cmp_nocase(params[0], "matchuu")==0) { + entry.set_match_uu(); + if (params.size() > 1 && cmp_nocase(params[1], "matchvv")==0) { + entry.set_match_vv(); + } + + } else if (cmp_nocase(params[0], "matchvv")==0) { + entry.set_match_vv(); + if (params.size() > 1 && cmp_nocase(params[1], "matchuu")==0) { + entry.set_match_uu(); + } + + } else if (cmp_nocase(params[0], "matchuv")==0) { + entry.set_match_uv(); + if (params.size() > 1 && cmp_nocase(params[1], "matchvu")==0) { + entry.set_match_vu(); + } + + } else if (cmp_nocase(params[0], "matchvu")==0) { + entry.set_match_vu(); + if (params.size() > 1 && cmp_nocase(params[1], "matchuv")==0) { + entry.set_match_uv(); + } + + } else if (cmp_nocase(params[0], "minu")==0) { + // minu #: minimum tesselation in U. + if (params.size() < 2) { + okflag = false; + } else { + int value = 0; + okflag = string_to_int(params[1], value); + entry.set_min_u(value); + } + + } else if (cmp_nocase(params[0], "minv")==0) { + // minu #: minimum tesselation in V. + if (params.size() < 2) { + okflag = false; + } else { + int value = 0; + okflag = string_to_int(params[1], value); + entry.set_min_v(value); + } + + } else if (tolower(params[0][0]) == 'i') { + // "i#": per-isoparam tesselation. + int value = 0; + okflag = string_to_int(params[0].substr(1), value); + entry.set_per_isoparam(value); + + } else if (params[0][params[0].length() - 1] == '%') { + double value = 0.0; + okflag = string_to_double(params[0].substr(0, params[0].length() - 1), value); + entry.set_importance(value / 100.0); + + } else if (params.size() == 1) { + // One numeric parameter: the number of triangles. + int value = 0; + okflag = string_to_int(params[0], value); + entry.set_num_tris(value); + + } else if (params.size() >= 2) { + // Two or more numeric parameters: the number of u by v quads, + // followed by an optional list of specific isoparams. + int u = 0, v = 0; + okflag = string_to_int(params[0], u) && string_to_int(params[1], v); + entry.set_uv(u, v, ¶ms[2], params.size() - 2); + + } else { + okflag = false; + } + + if (!okflag) { + qtess_cat.error() + << _filename << ": invalid parameters at line " + << line_number << ".\n"; + return false; + } + } + _entries.push_back(entry); + } + } + } + + if (qtess_cat.is_info()) { + qtess_cat.info() + << "read qtess parameter file " << _filename << ".\n"; + if (qtess_cat.is_debug()) { + write(qtess_cat.debug(false)); + } + } + + add_default_entry(); + + return true; +} + +/** + * Returns a reference to the last entry on the list, which is the "default" + * entry that will match any surface that does not get explicitly named in the + * input file. + */ +QtessInputEntry &QtessInputFile:: +get_default_entry() { + if (_entries.empty()) { + // No entries; create one. + add_default_entry(); + } + return _entries.back(); +} + + +/** + * Attempts to find a match for the given surface in the user input entries. + * Searches in the order in which the entries were defined, and chooses the + * first match. + * + * When a match is found, the surface is added to the entry's set of matched + * surfaces. Returns the type of the matching node if a match is found, or + * T_undefined otherwise. + */ +QtessInputEntry::Type QtessInputFile:: +match(QtessSurface *surface) { + QtessInputEntry::Type type; + + if (_entries.empty()) { + // No entries; create one. + add_default_entry(); + } + + Entries::iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + type = (*ei).match(surface); + if (type != QtessInputEntry::T_undefined) { + return type; + } + } + return QtessInputEntry::T_undefined; +} + +/** + * Determines the tesselation u,v amounts of each attached surface, and stores + * this information in the surface pointer. Returns the total number of tris + * that will be produced. + */ +int QtessInputFile:: +count_tris() { + int total_tris = 0; + + Entries::iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + total_tris += (*ei).count_tris(); + } + return total_tris; +} + +/** + * + */ +void QtessInputFile:: +write(std::ostream &out, int indent_level) const { + Entries::const_iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + (*ei).write(out, indent_level); + } +} + +/** + * Adds one more entry to the end of the list, to catch all of the surfaces + * that didn't get explicitly named. + */ +void QtessInputFile:: +add_default_entry() { + QtessInputEntry entry("*"); + entry.set_omit(); + _entries.push_back(entry); +} diff --git a/pandatool/src/egg-qtess/qtessInputFile.h b/pandatool/src/egg-qtess/qtessInputFile.h new file mode 100644 index 00000000..c5cb5fab --- /dev/null +++ b/pandatool/src/egg-qtess/qtessInputFile.h @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessInputFile.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef QTESSINPUTFILE_H +#define QTESSINPUTFILE_H + +#include "pandatoolbase.h" +#include "qtessInputEntry.h" +#include "filename.h" +#include "pvector.h" +#include "vector_double.h" + +class QtessSurface; + +/** + * Stores all the information read from a tesselation input file: a list of + * QtessInputEntry's. + */ +class QtessInputFile { +public: + QtessInputFile(); + INLINE QtessInputFile(const QtessInputFile ©); + INLINE void operator = (const QtessInputFile ©); + + bool read(const Filename &filename); + QtessInputEntry &get_default_entry(); + + QtessInputEntry::Type match(QtessSurface *surface); + int count_tris(); + + void write(std::ostream &out, int indent_level = 0) const; + +private: + void add_default_entry(); + + Filename _filename; + + typedef pvector Entries; + Entries _entries; +}; + +#include "qtessInputFile.I" + +#endif diff --git a/pandatool/src/egg-qtess/qtessSurface.I b/pandatool/src/egg-qtess/qtessSurface.I new file mode 100644 index 00000000..d65f82c3 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessSurface.I @@ -0,0 +1,154 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessSurface.I + * @author drose + * @date 2003-10-13 + */ + +/** + * + */ +INLINE const std::string &QtessSurface:: +get_name() const { + return _egg_surface->get_name(); +} + +/** + * Returns true if the defined surface is valid, false otherwise. + */ +INLINE bool QtessSurface:: +is_valid() const { + return (_nurbs != nullptr); +} + +/** + * Sets the importance of the surface, as a ratio in proportion to the square + * of its size. + */ +INLINE void QtessSurface:: +set_importance(double importance2) { + _importance = sqrt(importance2); + _importance2 = importance2; +} + +/** + * Indicates the surface to which this surface must match in its U direction. + * If u_to_u is true, it matches to the other surface's U direction; + * otherwise, it matches to the other surface's V direction. + * + * Note that the surface pointer is an indirect pointer. The value passed in + * is the address of the pointer to the actual surface (which may or may not + * be filled in yet). The actual pointer may be filled in later. + */ +INLINE void QtessSurface:: +set_match_u(QtessSurface **match_u, bool match_u_to_u) { + _match_u = match_u; + _match_u_to_u = match_u_to_u; +} + +/** + * Indicates the surface to which this surface must match in its V direction. + * If v_to_v is true, it matches to the other surface's V direction; + * otherwise, it matches to the other surface's U direction. + * + * Note that the surface pointer is an indirect pointer. The value passed in + * is the address of the pointer to the actual surface (which may or may not + * be filled in yet). The actual pointer may be filled in later. + */ +INLINE void QtessSurface:: +set_match_v(QtessSurface **match_v, bool match_v_to_v) { + _match_v = match_v; + _match_v_to_v = match_v_to_v; +} + +/** + * Specifies the absolute minimum number of segments allowed in the U + * direction. + */ +INLINE void QtessSurface:: +set_min_u(int min_u) { + _min_u = min_u; +} + +/** + * Specifies the absolute minimum number of segments allowed in the V + * direction. + */ +INLINE void QtessSurface:: +set_min_v(int min_v) { + _min_v = min_v; +} + + +/** + * Returns the number of patches the NURBS contains. Each patch is a square + * area bounded by isoparams. This actually scales by the importance of the + * surface, if it is not 1. + */ +INLINE double QtessSurface:: +count_patches() const { + return _num_u * _num_v * _importance2; +} + +/** + * Returns the number of triangles that will be generated by the current + * tesselation parameters. + */ +INLINE int QtessSurface:: +count_tris() const { + return _tess_u * _tess_v * 2; +} + +/** + * Returns the extra dimension number within the surface where the vertex + * membership in the indicated joint should be stored. + */ +INLINE int QtessSurface:: +get_joint_membership_index(EggGroup *joint) { + JointTable::iterator jti = _joint_table.find(joint); + if (jti != _joint_table.end()) { + return (*jti).second; + } + int d = _next_d; + _next_d++; + _joint_table[joint] = d; + return d; +} + +/** + * Returns the extra dimension number within the surface where the indicated + * Dxyz morph offset should be stored. + */ +INLINE int QtessSurface:: +get_dxyz_index(const std::string &morph_name) { + MorphTable::iterator mti = _dxyz_table.find(morph_name); + if (mti != _dxyz_table.end()) { + return (*mti).second; + } + int d = _next_d; + _next_d += 3; + _dxyz_table[morph_name] = d; + return d; +} + +/** + * Returns the extra dimension number within the surface where the indicated + * Drgba morph offset should be stored. + */ +INLINE int QtessSurface:: +get_drgba_index(const std::string &morph_name) { + MorphTable::iterator mti = _drgba_table.find(morph_name); + if (mti != _drgba_table.end()) { + return (*mti).second; + } + int d = _next_d; + _next_d += 4; + _drgba_table[morph_name] = d; + return d; +} diff --git a/pandatool/src/egg-qtess/qtessSurface.cxx b/pandatool/src/egg-qtess/qtessSurface.cxx new file mode 100644 index 00000000..d7ccebe7 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessSurface.cxx @@ -0,0 +1,565 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessSurface.cxx + * @author drose + * @date 2003-10-13 + */ + +#include "qtessSurface.h" +#include "qtessGlobals.h" +#include "qtessInputEntry.h" +#include "config_egg_qtess.h" +#include "eggPolygon.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggComment.h" +#include "egg_parametrics.h" +#include "pset.h" +#include "pmap.h" + +using std::max; +using std::string; + +/** + * + */ +QtessSurface:: +QtessSurface(EggNurbsSurface *egg_surface) : + _egg_surface(egg_surface) +{ + _nurbs = make_nurbs_surface(_egg_surface, LMatrix4d::ident_mat()); + _has_vertex_color = _egg_surface->has_vertex_color(); + + // The first four slots are reserved for vertex color. + _next_d = 4; + + _importance = 1.0; + _importance2 = 1.0; + _match_u = _match_v = nullptr; + _tess_u = _tess_v = 0; + _got_scores = false; + + // If the surface is closed in either dimension, the mininum tesselation in + // that dimension is by default 3, so we don't ribbonize the surface. + // Otherwise the minimum is 1. + _min_u = _min_v = 1; + if (egg_surface->is_closed_u()) { + _min_u = 3; + } + if (egg_surface->is_closed_v()) { + _min_v = 3; + } + + if (_nurbs == nullptr) { + _num_u = _num_v = 0; + + } else { + record_vertex_extras(); + + _nurbs->normalize_u_knots(); + _nurbs->normalize_v_knots(); + _nurbs_result = _nurbs->evaluate(); + + _num_u = _nurbs->get_num_u_segments(); + _num_v = _nurbs->get_num_v_segments(); + + if (QtessGlobals::_respect_egg) { + if (egg_surface->get_u_subdiv() != 0) { + _num_u = egg_surface->get_u_subdiv(); + } + if (egg_surface->get_v_subdiv() != 0) { + _num_v = egg_surface->get_v_subdiv(); + } + } + } +} + +/** + * Computes the curvature/stretch score for the surface, if it has not been + * already computed, and returns the net surface score. This is used both for + * automatically distributing isoparams among the surfaces by curvature, as + * well as for automatically placing the isoparams within each surface by + * curvature. + */ +double QtessSurface:: +get_score(double ratio) { + if (_nurbs == nullptr) { + return 0.0; + } + + if (!_got_scores) { + _u_placer.get_scores(_nurbs->get_num_u_segments() * 100, + _nurbs->get_num_v_segments() * 2, + ratio, _nurbs_result, true); + _v_placer.get_scores(_nurbs->get_num_v_segments() * 100, + _nurbs->get_num_u_segments() * 2, + ratio, _nurbs_result, false); + _got_scores = true; + } + + return _u_placer.get_total_score() * _v_placer.get_total_score() * _importance2; +} + +/** + * Applies the appropriate tesselation to the surface, and replaces its node + * in the tree with an EggGroup containing both the new vertex pool and all of + * the polygons. + */ +int QtessSurface:: +tesselate() { + apply_match(); + int tris = 0; + + PT(EggGroup) group = do_uniform_tesselate(tris); + PT(EggNode) new_node = group.p(); + if (new_node == nullptr) { + new_node = new EggComment(_egg_surface->get_name(), + "Omitted NURBS surface."); + tris = 0; + } + EggGroupNode *parent = _egg_surface->get_parent(); + nassertr(parent != nullptr, 0); + parent->remove_child(_egg_surface); + parent->add_child(new_node); + + return tris; +} + +/** + * Writes a line to the given output file telling qtess how this surface + * should be tesselated uniformly. Returns the number of tris. + */ +int QtessSurface:: +write_qtess_parameter(std::ostream &out) { + apply_match(); + + if (_tess_u == 0 || _tess_v == 0) { + out << get_name() << " : omit\n"; + + } else if (_iso_u.empty() || _iso_v.empty()) { + out << get_name() << " : " << _tess_u << " " << _tess_v << "\n"; + + } else { + out << get_name() << " : " << _iso_u.back() << " " << _iso_v.back(); + QtessInputEntry::output_extra(out, _iso_u, 'u'); + QtessInputEntry::output_extra(out, _iso_v, 'v'); + out << "\n"; + } + + return count_tris(); +} + + +/** + * Sets up the surface to omit itself from the output. + */ +void QtessSurface:: +omit() { + _tess_u = 0; + _tess_v = 0; +} + +/** + * Sets the surface up to tesselate itself uniformly at u x v, or if autoplace + * is true, automatically with u x v quads. + */ +void QtessSurface:: +tesselate_uv(int u, int v, bool autoplace, double ratio) { + _tess_u = u; + _tess_v = v; + _iso_u.clear(); + _iso_v.clear(); + if (autoplace) { + tesselate_auto(_tess_u, _tess_v, ratio); + } +} + +/** + * Sets the surface up to tesselate itself at specific isoparams only. + */ +void QtessSurface:: +tesselate_specific(const pvector &u_list, + const pvector &v_list) { + _iso_u = u_list; + _iso_v = v_list; + _tess_u = (int)_iso_u.size() - 1; + _tess_v = (int)_iso_v.size() - 1; +} + +/** + * Sets the surface up to tesselate itself to a uniform amount per isoparam. + */ +void QtessSurface:: +tesselate_per_isoparam(double pi, bool autoplace, double ratio) { + if (_num_u == 0 || _num_v == 0) { + omit(); + + } else { + _tess_u = max(_min_u, (int)floor(_num_u * _importance * pi + 0.5)); + _tess_v = max(_min_v, (int)floor(_num_v * _importance * pi + 0.5)); + _iso_u.clear(); + _iso_v.clear(); + if (autoplace) { + tesselate_auto(_tess_u, _tess_v, ratio); + } + } +} + + +/** + * Sets the surface up to tesselate itself according to its computed curvature + * score in both dimensions. + */ +void QtessSurface:: +tesselate_per_score(double pi, bool autoplace, double ratio) { + if (get_score(ratio) <= 0.0) { + omit(); + + } else { + _tess_u = max(_min_u, (int)floor(_u_placer.get_total_score() * _importance * pi + 0.5)); + _tess_v = max(_min_v, (int)floor(_v_placer.get_total_score() * _importance * pi + 0.5)); + _iso_u.clear(); + _iso_v.clear(); + if (autoplace) { + tesselate_auto(_tess_u, _tess_v, ratio); + } + } +} + +/** + * Sets the surface up to tesselate itself by automatically determining the + * best place to put the indicated u x v isoparams. + */ +void QtessSurface:: +tesselate_auto(int u, int v, double ratio) { + if (get_score(ratio) <= 0.0) { + omit(); + + } else { + _u_placer.place(u, _iso_u); + _v_placer.place(v, _iso_v); + _tess_u = (int)_iso_u.size() - 1; + _tess_v = (int)_iso_v.size() - 1; + } +} + +/** + * Records the joint membership and morph offsets of each control vertex in + * the extra-dimensional space of the NURBS, so that we can extract this data + * out again later to apply to the polygon vertices. + */ +void QtessSurface:: +record_vertex_extras() { + int num_u_vertices = _egg_surface->get_num_u_cvs(); + int num_v_vertices = _egg_surface->get_num_v_cvs(); + + for (int ui = 0; ui < num_u_vertices; ui++) { + for (int vi = 0; vi < num_v_vertices; vi++) { + int i = _egg_surface->get_vertex_index(ui, vi); + EggVertex *egg_vertex = _egg_surface->get_vertex(i); + + // The joint membership. + EggVertex::GroupRef::const_iterator gi; + for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) { + EggGroup *joint = (*gi); + int d = get_joint_membership_index(joint); + double membership = joint->get_vertex_membership(egg_vertex); + _nurbs->set_extended_vertex(ui, vi, d, membership); + } + + // The xyz morphs. + EggMorphVertexList::const_iterator dxi; + for (dxi = egg_vertex->_dxyzs.begin(); + dxi != egg_vertex->_dxyzs.end(); + ++dxi) { + const string &morph_name = (*dxi).get_name(); + LVector3 delta = LCAST(PN_stdfloat, (*dxi).get_offset()); + int d = get_dxyz_index(morph_name); + _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 3); + } + + // The rgba morphs. + EggMorphColorList::const_iterator dri; + for (dri = egg_vertex->_drgbas.begin(); + dri != egg_vertex->_drgbas.end(); + ++dri) { + const string &morph_name = (*dri).get_name(); + const LVector4 &delta = (*dri).get_offset(); + int d = get_drgba_index(morph_name); + _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 4); + } + } + } +} + +/** + * If the surface was set up to copy its tesselation in either axis from + * another surface, makes this copy now. + */ +void QtessSurface:: +apply_match() { + if (_match_u != nullptr) { + QtessSurface *m = *_match_u; + if (m == nullptr) { + qtess_cat.warning() + << "No surface to match " << get_name() << " to in U.\n"; + } else { + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << "Matching " << get_name() << " in U to " << m->get_name() + << " in " << (_match_u_to_u?'U':'V') << ".\n"; + } + if (_match_u_to_u) { + _tess_u = m->_tess_u; + _iso_u = m->_iso_u; + } else { + _tess_u = m->_tess_v; + _iso_u = m->_iso_v; + } + } + } + + if (_match_v != nullptr) { + QtessSurface *m = *_match_v; + if (m == nullptr) { + qtess_cat.warning() + << "No surface to match " << get_name() << " in V.\n"; + } else { + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << "Matching " << get_name() << " in V to " << m->get_name() + << " in " << (_match_v_to_v?'V':'U') << ".\n"; + } + if (_match_v_to_v) { + _tess_v = m->_tess_v; + _iso_v = m->_iso_v; + } else { + _tess_v = m->_tess_u; + _iso_v = m->_iso_u; + } + } + } +} + +/** + * Subdivide the surface uniformly according to the parameters specified by an + * earlier call to omit(), teseselate_uv(), or tesselate_per_isoparam(). + */ +PT(EggGroup) QtessSurface:: +do_uniform_tesselate(int &tris) const { + tris = 0; + + if (_tess_u == 0 || _tess_v == 0) { + // No tesselation! + if (qtess_cat.is_debug()) { + qtess_cat.debug() + << get_name() << " : omit\n"; + } + return nullptr; + } + + PT(EggGroup) group = new EggGroup(_egg_surface->get_name()); + + // _tess_u and _tess_v are the number of patches to create. Convert that to + // the number of vertices. + + int num_u = _tess_u + 1; + int num_v = _tess_v + 1; + + if (qtess_cat.is_debug()) { + qtess_cat.debug() << get_name() << " : " << tris << "\n"; + } + + assert(_iso_u.empty() || (int)_iso_u.size() == num_u); + assert(_iso_v.empty() || (int)_iso_v.size() == num_v); + + // Now how many vertices is that total, and how many vertices per strip? + int num_verts = num_u * num_v; + + // Create a vertex pool. + PT(EggVertexPool) vpool = new EggVertexPool(_egg_surface->get_name()); + group->add_child(vpool); + + // Create all the vertices. + int ui, vi; + double u, v; + + typedef pvector VertexList; + VertexList new_verts; + new_verts.reserve(num_verts); + + // Also collect the vertices into this set to group them by spatial position + // only. This is relevant for calculating normals. + typedef pset NVertexGroup; + typedef pmap NVertexCollection; + NVertexCollection n_collection; + + for (vi = 0; vi < num_v; vi++) { + if (_iso_v.empty()) { + v = (double)vi / (double)(num_v-1); + } else { + v = _iso_v[vi] / _iso_v.back(); + } + for (ui = 0; ui < num_u; ui++) { + if (_iso_u.empty()) { + u = (double)ui / (double)(num_u-1); + } else { + u = _iso_u[ui] / _iso_u.back(); + } + + PT(EggVertex) egg_vertex = evaluate_vertex(u, v); + vpool->add_vertex(egg_vertex); + new_verts.push_back(egg_vertex); + n_collection[egg_vertex->get_pos3()].insert(egg_vertex); + } + } + nassertr((int)new_verts.size() == num_verts, nullptr); + + // Now create a bunch of quads. + for (vi = 1; vi < num_v; vi++) { + for (ui = 1; ui < num_u; ui++) { + PT(EggPolygon) poly = new EggPolygon; + poly->add_vertex(new_verts[vi*num_u + (ui-1)]); + poly->add_vertex(new_verts[(vi-1)*num_u + (ui-1)]); + poly->add_vertex(new_verts[(vi-1)*num_u + ui]); + poly->add_vertex(new_verts[vi*num_u + ui]); + + poly->copy_attributes(*_egg_surface); + + // We compute a polygon normal just so we can verify the calculated + // vertex normals. It's also helpful for identifying degenerate + // polygons. + if (poly->recompute_polygon_normal()) { + tris += 2; + group->add_child(poly); + } + } + } + + // Now check all the vertex normals by comparing them to the polygon + // normals. Some might have not been computed at all; others might be + // facing in the wrong direction. + + // Now go back through and normalize the computed normals. + NVertexCollection::const_iterator nci; + for (nci = n_collection.begin(); nci != n_collection.end(); ++nci) { + const NVertexGroup &group = (*nci).second; + + // Calculate the normal these vertices should have based on the polygons + // that share it. + LNormald normal = LNormald::zero(); + int num_polys = 0; + NVertexGroup::const_iterator ngi; + for (ngi = group.begin(); ngi != group.end(); ++ngi) { + EggVertex *egg_vertex = (*ngi); + EggVertex::PrimitiveRef::const_iterator pri; + for (pri = egg_vertex->pref_begin(); + pri != egg_vertex->pref_end(); + ++pri) { + EggPrimitive *egg_primitive = (*pri); + nassertr(egg_primitive->has_normal(), nullptr); + normal += egg_primitive->get_normal(); + num_polys++; + } + } + + if (num_polys > 0) { + normal /= (double)num_polys; + + // Now compare this normal with what the NURBS representation + // calculated. It should be facing in at least vaguely the same + // direction. + for (ngi = group.begin(); ngi != group.end(); ++ngi) { + EggVertex *egg_vertex = (*ngi); + if (egg_vertex->has_normal()) { + if (normal.dot(egg_vertex->get_normal()) < 0.0) { + // This one is backwards. + egg_vertex->set_normal(-egg_vertex->get_normal()); + } + } else { + // This vertex doesn't have a normal; it gets the computed normal. + egg_vertex->set_normal(normal); + } + } + } + } + + return group; +} + +/** + * Evaluates the surface at the given u, v position and sets the vertex to the + * appropriate values. Also sets the joint membership of the vertex. + */ +PT(EggVertex) QtessSurface:: +evaluate_vertex(double u, double v) const { + PT(EggVertex) egg_vertex = new EggVertex; + + LVertex point; + LNormal normal; + _nurbs_result->eval_point(u, v, point); + _nurbs_result->eval_normal(u, v, normal); + + // If the normal is too short, don't consider it--it's probably inaccurate + // due to numerical limitations. We'll recompute it later based on the + // polygon normals. + PN_stdfloat length = normal.length(); + if (length > 0.0001f) { + normal /= length; + egg_vertex->set_normal(LCAST(double, normal)); + } + + egg_vertex->set_pos(LCAST(double, point)); + egg_vertex->set_uv(LVecBase2d(u, v)); + + // The color is stored, by convention, in slots 0-4 of the surface. + if (_has_vertex_color) { + LColor rgba; + _nurbs_result->eval_extended_points(u, v, 0, &rgba[0], 4); + egg_vertex->set_color(rgba); + } + + // Also fill in the joint membership. + JointTable::const_iterator jti; + for (jti = _joint_table.begin(); jti != _joint_table.end(); ++jti) { + EggGroup *joint = (*jti).first; + int d = (*jti).second; + + double membership = _nurbs_result->eval_extended_point(u, v, d); + if (membership > 0.0) { + joint->ref_vertex(egg_vertex, membership); + } + } + + // And the morphs. + MorphTable::const_iterator mti; + for (mti = _dxyz_table.begin(); mti != _dxyz_table.end(); ++mti) { + const string &morph_name = (*mti).first; + int d = (*mti).second; + + LVector3 delta; + _nurbs_result->eval_extended_points(u, v, d, &delta[0], 3); + if (!delta.almost_equal(LVector3::zero())) { + egg_vertex->_dxyzs.insert(EggMorphVertex(morph_name, LCAST(double, delta))); + } + } + + for (mti = _drgba_table.begin(); mti != _drgba_table.end(); ++mti) { + const string &morph_name = (*mti).first; + int d = (*mti).second; + + LVector4 delta; + _nurbs_result->eval_extended_points(u, v, d, &delta[0], 4); + if (!delta.almost_equal(LVector4::zero())) { + egg_vertex->_drgbas.insert(EggMorphColor(morph_name, delta)); + } + } + + return egg_vertex; +} diff --git a/pandatool/src/egg-qtess/qtessSurface.h b/pandatool/src/egg-qtess/qtessSurface.h new file mode 100644 index 00000000..fc3720e5 --- /dev/null +++ b/pandatool/src/egg-qtess/qtessSurface.h @@ -0,0 +1,112 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file qtessSurface.h + * @author drose + * @date 2003-10-13 + */ + +#ifndef QTESSSURFACE_H +#define QTESSSURFACE_H + +#include "pandatoolbase.h" +#include "isoPlacer.h" +#include "eggNurbsSurface.h" +#include "eggGroup.h" +#include "eggVertex.h" +#include "nurbsSurfaceEvaluator.h" +#include "nurbsSurfaceResult.h" +#include "referenceCount.h" +#include "pointerTo.h" + +/** + * A reference to an EggNurbsSurface in the egg file, and its parameters as + * set by the user input file and as computed in relation to the other + * surfaces. + */ +class QtessSurface : public ReferenceCount { +public: + QtessSurface(EggNurbsSurface *egg_surface); + + INLINE const std::string &get_name() const; + INLINE bool is_valid() const; + + INLINE void set_importance(double importance2); + INLINE void set_match_u(QtessSurface **match_u, bool match_u_to_u); + INLINE void set_match_v(QtessSurface **match_v, bool match_v_to_v); + INLINE void set_min_u(int min_u); + INLINE void set_min_v(int min_v); + + INLINE double count_patches() const; + INLINE int count_tris() const; + + double get_score(double ratio); + + int tesselate(); + int write_qtess_parameter(std::ostream &out); + void omit(); + void tesselate_uv(int u, int v, bool autoplace, double ratio); + void tesselate_specific(const pvector &u_list, + const pvector &v_list); + void tesselate_per_isoparam(double pi, bool autoplace, double ratio); + void tesselate_per_score(double pi, bool autoplace, double ratio); + void tesselate_auto(int u, int v, double ratio); + +private: + void record_vertex_extras(); + INLINE int get_joint_membership_index(EggGroup *joint); + INLINE int get_dxyz_index(const std::string &morph_name); + INLINE int get_drgba_index(const std::string &morph_name); + + void apply_match(); + PT(EggGroup) do_uniform_tesselate(int &tris) const; + PT(EggVertex) evaluate_vertex(double u, double v) const; + + PT(EggNurbsSurface) _egg_surface; + PT(NurbsSurfaceEvaluator) _nurbs; + PT(NurbsSurfaceResult) _nurbs_result; + bool _has_vertex_color; + + // Mapping arbitrary attributes to integer extended dimension values, so we + // can hang arbitrary data in the extra dimensional space of the surface. + int _next_d; + typedef std::map JointTable; + JointTable _joint_table; + typedef std::map MorphTable; + MorphTable _dxyz_table; + MorphTable _drgba_table; + + int _num_u, _num_v; + int _tess_u, _tess_v; + pvector _iso_u, _iso_v; // If nonempty, isoparams at which to tess. + + // _importance is the relative importance of the surface along either axis; + // _importance2 is this number squared, which is the value set by + // set_importance(). + double _importance; + double _importance2; + + // _match_u and _match_v indicate which surface we must match exactly for + // tesselation in U or V. This helps get edges to line up properly. They + // are indirect pointers because we go through the surfaces in one pass, and + // might need to fill in the correct value later. + QtessSurface **_match_u, **_match_v; + bool _match_u_to_u, _match_v_to_v; + + // _min_u and _min_v specify a mininum number of quads below which we should + // not attempt to subdivide the surface in either dimension. This is + // intended to prevent degenerate cases like knife-fingers. + int _min_u, _min_v; + + IsoPlacer _u_placer, _v_placer; + bool _got_scores; +}; + +#include "qtessSurface.I" + +#endif diff --git a/pandatool/src/egg-qtess/subdivSegment.I b/pandatool/src/egg-qtess/subdivSegment.I new file mode 100644 index 00000000..2210032e --- /dev/null +++ b/pandatool/src/egg-qtess/subdivSegment.I @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file subdivSegment.I + * @author drose + * @date 2003-10-14 + */ + +/** + * + */ +INLINE SubdivSegment:: +SubdivSegment(const double *cint, int f, int t) : + _cint(cint), + _f(f), + _t(t) +{ +} + +/** + * Returns the net score of the segment. + */ +INLINE double SubdivSegment:: +get_score() const { + return _cint[_t] - _cint[_f]; +} + +/** + * Returns a score that indicates how badly the segment needs to be further + * subdivided. The greater the number, the greater the need. + */ +INLINE double SubdivSegment:: +get_need() const { + return get_score() / (double)(_num_cuts+1); +} + +/** + * Sorts the segments in descending order of need. + */ +INLINE bool SubdivSegment:: +operator < (const SubdivSegment &other) const { + return get_need() > other.get_need(); +} diff --git a/pandatool/src/egg-qtess/subdivSegment.cxx b/pandatool/src/egg-qtess/subdivSegment.cxx new file mode 100644 index 00000000..3c036e56 --- /dev/null +++ b/pandatool/src/egg-qtess/subdivSegment.cxx @@ -0,0 +1,79 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file subdivSegment.cxx + * @author drose + * @date 2003-10-14 + */ + +#include "subdivSegment.h" + + + + +/** + * Performs a standard binary search. This utility function is used below. + */ +static int +binary_search(double val, const double *array, int bot, int top) { + if (top < bot) { + return bot; + } + int mid = (bot + top)/2; + + if (array[mid] < val) { + return binary_search(val, array, mid+1, top); + } else { + return binary_search(val, array, bot, mid-1); + } +} + + +/** + * Applies _num_cuts cuts to the segment. + */ +void SubdivSegment:: +cut() { + int c; + double ct = get_score(); + + _cuts.erase(_cuts.begin(), _cuts.end()); + int last = _f; + for (c = 1; c < _num_cuts+1; c++) { + double val = (double)c * ct / (double)(_num_cuts+1) + _cint[_f]; + int i = binary_search(val, _cint, _f, _t); + if (i != last && i < _t) { + _cuts.push_back(i); + } + last = i; + } + + while ((int)_cuts.size() < _num_cuts) { + // Do we have any extra? Assign them into likely places. + int last = _f; + int mc = -1; + int mv = 0; + for (c = 0; c < (int)_cuts.size(); c++) { + if (mc == -1 || _cuts[c] - last > mv) { + mc = c; + mv = _cuts[c] - last; + } + last = _cuts[c]; + } + + if (mc==-1) { + // Surrender. + return; + } + if (mc==0) { + _cuts.insert(_cuts.begin() + mc, (_cuts[mc] + _f) / 2); + } else { + _cuts.insert(_cuts.begin() + mc, (_cuts[mc] + _cuts[mc-1]) / 2); + } + } +} diff --git a/pandatool/src/egg-qtess/subdivSegment.h b/pandatool/src/egg-qtess/subdivSegment.h new file mode 100644 index 00000000..abe6e58a --- /dev/null +++ b/pandatool/src/egg-qtess/subdivSegment.h @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file subdivSegment.h + * @author drose + * @date 2003-10-14 + */ + +#ifndef SUBDIVSEGMENT_H +#define SUBDIVSEGMENT_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "vector_int.h" + +/** + * Represents a single hypothetical subdivided segment, under consideration by + * the IsoPlacer. + */ +class SubdivSegment { +public: + INLINE SubdivSegment(const double *cint, int f, int t); + + INLINE double get_score() const; + INLINE double get_need() const; + INLINE bool operator < (const SubdivSegment &other) const; + + void cut(); + + const double *_cint; + int _f, _t; + int _num_cuts; + vector_int _cuts; +}; + +#include "subdivSegment.I" + +#endif diff --git a/pandatool/src/eggbase/CMakeLists.txt b/pandatool/src/eggbase/CMakeLists.txt new file mode 100644 index 00000000..892e8bb8 --- /dev/null +++ b/pandatool/src/eggbase/CMakeLists.txt @@ -0,0 +1,33 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3EGGBASE_HEADERS + eggBase.h eggConverter.h eggFilter.h + eggMakeSomething.h + eggMultiBase.h eggMultiFilter.h + eggReader.h eggSingleBase.h + eggToSomething.h eggWriter.h + somethingToEgg.h +) + +set(P3EGGBASE_SOURCES + eggBase.cxx eggConverter.cxx eggFilter.cxx + eggMakeSomething.cxx + eggMultiBase.cxx + eggMultiFilter.cxx eggReader.cxx eggSingleBase.cxx + eggToSomething.cxx + eggWriter.cxx somethingToEgg.cxx +) + +composite_sources(p3eggbase P3EGGBASE_SOURCES) +add_library(p3eggbase STATIC ${P3EGGBASE_HEADERS} ${P3EGGBASE_SOURCES}) +target_link_libraries(p3eggbase p3progbase p3converter) + +install(TARGETS p3eggbase + EXPORT ToolsDevel COMPONENT ToolsDevel + DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d + ARCHIVE COMPONENT ToolsDevel) +install(FILES ${P3EGGBASE_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) diff --git a/pandatool/src/eggbase/eggBase.cxx b/pandatool/src/eggbase/eggBase.cxx new file mode 100644 index 00000000..83d07a0a --- /dev/null +++ b/pandatool/src/eggbase/eggBase.cxx @@ -0,0 +1,405 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggBase.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "eggBase.h" + +#include "eggGroupNode.h" +#include "eggTexture.h" +#include "eggFilenameNode.h" +#include "eggComment.h" +#include "dcast.h" +#include "string_utils.h" + +using std::string; + +/** + * + */ +EggBase:: +EggBase() { + add_option + ("cs", "coordinate-system", 80, + "Specify the coordinate system to operate in. This may be one of " + "'y-up', 'z-up', 'y-up-left', or 'z-up-left'.", + &EggBase::dispatch_coordinate_system, + &_got_coordinate_system, &_coordinate_system); + + _normals_mode = NM_preserve; + _normals_threshold = 0.0; + + _got_tbnall = false; + _got_tbnauto = false; + _make_points = false; + + _got_transform = false; + _transform = LMatrix4d::ident_mat(); + + _got_coordinate_system = false; + _coordinate_system = CS_yup_right; + + _noabs = false; +} + +/** + * Adds -no, -np, etc. as valid options for this program. If the user + * specifies one of the options on the command line, the normals will be + * adjusted when the egg file is written out. + */ +void EggBase:: +add_normals_options() { + static NormalsMode strip = NM_strip; + static NormalsMode polygon = NM_polygon; + static NormalsMode vertex = NM_vertex; + static NormalsMode preserve = NM_preserve; + + add_option + ("no", "", 48, + "Strip all normals.", + &EggBase::dispatch_normals, nullptr, &strip); + + add_option + ("np", "", 48, + "Strip existing normals and redefine polygon normals.", + &EggBase::dispatch_normals, nullptr, &polygon); + + add_option + ("nv", "threshold", 48, + "Strip existing normals and redefine vertex normals. Consider an edge " + "between adjacent polygons to be smooth if the angle between them " + "is less than threshold degrees.", + &EggBase::dispatch_normals, nullptr, &vertex); + + add_option + ("nn", "", 48, + "Preserve normals exactly as they are. This is the default.", + &EggBase::dispatch_normals, nullptr, &preserve); + + add_option + ("tbn", "name", 48, + "Compute tangent and binormal for the named texture coordinate " + "set(s). The name may include wildcard characters such as * and ?. " + "The normal must already exist or have been computed via one of the " + "above options. The tangent and binormal are used to implement " + "bump mapping and related texture-based lighting effects. This option " + "may be repeated as necessary to name multiple texture coordinate sets.", + &EggBase::dispatch_vector_string, nullptr, &_tbn_names); + + add_option + ("tbnall", "", 48, + "Compute tangent and binormal for all texture coordinate " + "sets. This is equivalent to -tbn \"*\".", + &EggBase::dispatch_none, &_got_tbnall); + + add_option + ("tbnauto", "", 48, + "Compute tangent and binormal for all normal maps. ", + &EggBase::dispatch_none, &_got_tbnauto); +} + +/** + * Adds -points as a valid option for this program. + */ +void EggBase:: +add_points_options() { + add_option + ("points", "", 46, + "Construct entries for any unreferenced vertices, to make them visible.", + &EggBase::dispatch_none, &_make_points); +} + +/** + * Adds -TS, -TT, etc. as valid options for this program. If the user + * specifies one of the options on the command line, the data will be + * transformed when the egg file is written out. + */ +void EggBase:: +add_transform_options() { + add_option + ("TS", "sx[,sy,sz]", 49, + "Scale the model uniformly by the given factor (if only one number " + "is given) or in each axis by sx, sy, sz (if three numbers are given).", + &EggBase::dispatch_scale, &_got_transform, &_transform); + + add_option + ("TR", "x,y,z", 49, + "Rotate the model x degrees about the x axis, then y degrees about the " + "y axis, and then z degrees about the z axis.", + &EggBase::dispatch_rotate_xyz, &_got_transform, &_transform); + + add_option + ("TA", "angle,x,y,z", 49, + "Rotate the model angle degrees counterclockwise about the given " + "axis.", + &EggBase::dispatch_rotate_axis, &_got_transform, &_transform); + + add_option + ("TT", "x,y,z", 49, + "Translate the model by the indicated amount.\n\n" + "All transformation options (-TS, -TR, -TA, -TT) are cumulative and are " + "applied in the order they are encountered on the command line.", + &EggBase::dispatch_translate, &_got_transform, &_transform); +} + +/** + * Recursively walks the egg hierarchy. Any filenames encountered are + * replaced according to the indicated PathReplace. + */ +void EggBase:: +convert_paths(EggNode *node, PathReplace *path_replace, + const DSearchPath &additional_path) { + if (node->is_of_type(EggTexture::get_class_type())) { + EggTexture *egg_tex = DCAST(EggTexture, node); + Filename fullpath, outpath; + path_replace->full_convert_path(egg_tex->get_filename(), additional_path, + fullpath, outpath); + egg_tex->set_filename(outpath); + egg_tex->set_fullpath(fullpath); + + if (egg_tex->has_alpha_filename()) { + Filename alpha_fullpath, alpha_outpath; + path_replace->full_convert_path(egg_tex->get_alpha_filename(), additional_path, + alpha_fullpath, alpha_outpath); + egg_tex->set_alpha_filename(alpha_outpath); + egg_tex->set_alpha_fullpath(alpha_fullpath); + } + + } else if (node->is_of_type(EggFilenameNode::get_class_type())) { + EggFilenameNode *egg_fnode = DCAST(EggFilenameNode, node); + + Filename fullpath, outpath; + path_replace->full_convert_path(egg_fnode->get_filename(), additional_path, + fullpath, outpath); + egg_fnode->set_filename(outpath); + egg_fnode->set_fullpath(fullpath); + + } else if (node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *egg_group = DCAST(EggGroupNode, node); + EggGroupNode::const_iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + convert_paths(*ci, path_replace, additional_path); + } + } +} + +/** + * Inserts a comment into the beginning of the indicated egg file + * corresponding to the command line that invoked this program. + * + * Normally this function is called automatically when appropriate by + * EggWriter, and it's not necessary to call it explicitly. + */ +void EggBase:: +append_command_comment(EggData *data) { + append_command_comment(data, get_exec_command()); +} + +/** + * Inserts a comment into the beginning of the indicated egg file + * corresponding to the command line that invoked this program. + * + * Normally this function is called automatically when appropriate by + * EggWriter, and it's not necessary to call it explicitly. + */ +void EggBase:: +append_command_comment(EggData *data, const string &comment) { + data->insert(data->begin(), new EggComment("", comment)); +} + +/** + * Accepts one of -no, -np, etc. and sets _normals_mode as indicated. The + * void * argument is a pointer to a NormalsMode variable that indicates which + * switch was passed. + */ +bool EggBase:: +dispatch_normals(ProgramBase *self, const string &opt, const string &arg, void *mode) { + EggBase *base = (EggBase *)self; + return base->ns_dispatch_normals(opt, arg, mode); +} + +/** + * Accepts one of -no, -np, etc. and sets _normals_mode as indicated. The + * void * argument is a pointer to a NormalsMode variable that indicates which + * switch was passed. + */ +bool EggBase:: +ns_dispatch_normals(const string &opt, const string &arg, void *mode) { + _normals_mode = *(NormalsMode *)mode; + + if (_normals_mode == NM_vertex) { + if (!string_to_double(arg, _normals_threshold)) { + nout << "Invalid numeric parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + } + + return true; +} + +/** + * Handles -TS, which specifies a scale transform. Var is an LMatrix4d. + */ +bool EggBase:: +dispatch_scale(const string &opt, const string &arg, void *var) { + LMatrix4d *transform = (LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + double sx, sy, sz; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], sx) && + string_to_double(words[1], sy) && + string_to_double(words[2], sz); + + } else if (words.size() == 1) { + okflag = + string_to_double(words[0], sx); + sy = sz = sx; + } + + if (!okflag) { + nout << "-" << opt + << " requires one or three numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4d::scale_mat(sx, sy, sz); + + return true; +} + +/** + * Handles -TR, which specifies a rotate transform about the three cardinal + * axes. Var is an LMatrix4d. + */ +bool EggBase:: +dispatch_rotate_xyz(ProgramBase *self, const string &opt, const string &arg, void *var) { + EggBase *base = (EggBase *)self; + return base->ns_dispatch_rotate_xyz(opt, arg, var); +} + +/** + * Handles -TR, which specifies a rotate transform about the three cardinal + * axes. Var is an LMatrix4d. + */ +bool EggBase:: +ns_dispatch_rotate_xyz(const string &opt, const string &arg, void *var) { + LMatrix4d *transform = (LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + LVecBase3d xyz; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], xyz[0]) && + string_to_double(words[1], xyz[1]) && + string_to_double(words[2], xyz[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + LMatrix4d mat = + LMatrix4d::rotate_mat(xyz[0], LVector3d(1.0, 0.0, 0.0), _coordinate_system) * + LMatrix4d::rotate_mat(xyz[1], LVector3d(0.0, 1.0, 0.0), _coordinate_system) * + LMatrix4d::rotate_mat(xyz[2], LVector3d(0.0, 0.0, 1.0), _coordinate_system); + + *transform = (*transform) * mat; + + return true; +} + +/** + * Handles -TA, which specifies a rotate transform about an arbitrary axis. + * Var is an LMatrix4d. + */ +bool EggBase:: +dispatch_rotate_axis(ProgramBase *self, const string &opt, const string &arg, void *var) { + EggBase *base = (EggBase *)self; + return base->ns_dispatch_rotate_axis(opt, arg, var); +} + +/** + * Handles -TA, which specifies a rotate transform about an arbitrary axis. + * Var is an LMatrix4d. + */ +bool EggBase:: +ns_dispatch_rotate_axis(const string &opt, const string &arg, void *var) { + LMatrix4d *transform = (LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + double angle; + LVecBase3d axis; + + bool okflag = false; + if (words.size() == 4) { + okflag = + string_to_double(words[0], angle) && + string_to_double(words[1], axis[0]) && + string_to_double(words[2], axis[1]) && + string_to_double(words[3], axis[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires four numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4d::rotate_mat(angle, axis, _coordinate_system); + + return true; +} + +/** + * Handles -TT, which specifies a translate transform. Var is an LMatrix4d. + */ +bool EggBase:: +dispatch_translate(const string &opt, const string &arg, void *var) { + LMatrix4d *transform = (LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + LVector3d trans; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], trans[0]) && + string_to_double(words[1], trans[1]) && + string_to_double(words[2], trans[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4d::translate_mat(trans); + + return true; +} diff --git a/pandatool/src/eggbase/eggBase.h b/pandatool/src/eggbase/eggBase.h new file mode 100644 index 00000000..4c6b1567 --- /dev/null +++ b/pandatool/src/eggbase/eggBase.h @@ -0,0 +1,78 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggBase.h + * @author drose + * @date 2000-02-14 + */ + +#ifndef EGGBASE_H +#define EGGBASE_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "eggData.h" + +/** + * This is a base class for both EggSingleBase and EggMultiBase. Don't + * inherit directly from this; use one of those two classes instead. + * + * This is just a base class; see EggReader, EggWriter, or EggFilter according + * to your particular I/O needs. + */ +class EggBase : public ProgramBase { +public: + EggBase(); + + void add_normals_options(); + void add_points_options(); + void add_transform_options(); + + static void convert_paths(EggNode *node, PathReplace *path_replace, + const DSearchPath &additional_path); + +protected: + void append_command_comment(EggData *_data); + static void append_command_comment(EggData *_data, const std::string &comment); + + static bool dispatch_normals(ProgramBase *self, const std::string &opt, const std::string &arg, void *mode); + bool ns_dispatch_normals(const std::string &opt, const std::string &arg, void *mode); + + static bool dispatch_scale(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_rotate_xyz(ProgramBase *self, const std::string &opt, const std::string &arg, void *var); + bool ns_dispatch_rotate_xyz(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_rotate_axis(ProgramBase *self, const std::string &opt, const std::string &arg, void *var); + bool ns_dispatch_rotate_axis(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_translate(const std::string &opt, const std::string &arg, void *var); + +protected: + enum NormalsMode { + NM_strip, + NM_polygon, + NM_vertex, + NM_preserve + }; + NormalsMode _normals_mode; + double _normals_threshold; + vector_string _tbn_names; + bool _got_tbnall; + bool _got_tbnauto; + + bool _make_points; + + bool _got_transform; + LMatrix4d _transform; + + bool _got_coordinate_system; + CoordinateSystem _coordinate_system; + + bool _noabs; +}; + +#endif diff --git a/pandatool/src/eggbase/eggConverter.cxx b/pandatool/src/eggbase/eggConverter.cxx new file mode 100644 index 00000000..036b7dea --- /dev/null +++ b/pandatool/src/eggbase/eggConverter.cxx @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggConverter.cxx + * @author drose + * @date 2000-02-15 + */ + +#include "eggConverter.h" + +/** + * The first parameter to the constructor should be the one-word name of the + * alien file format that is to be read or written, for instance "OpenFlight" + * or "Alias". It's just used in printing error messages and such. The + * second parameter is the preferred extension of files of this form, if any, + * with a leading dot. + */ +EggConverter:: +EggConverter(const std::string &format_name, + const std::string &preferred_extension, + bool allow_last_param, + bool allow_stdout) : + EggFilter(allow_last_param, allow_stdout), + _format_name(format_name) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = preferred_extension; +} diff --git a/pandatool/src/eggbase/eggConverter.h b/pandatool/src/eggbase/eggConverter.h new file mode 100644 index 00000000..3922a6b8 --- /dev/null +++ b/pandatool/src/eggbase/eggConverter.h @@ -0,0 +1,36 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggConverter.h + * @author drose + * @date 2000-02-15 + */ + +#ifndef EGGCONVERTER_H +#define EGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "eggFilter.h" + +/** + * This is a general base class for programs that convert between egg files + * and some other format. See EggToSomething and SomethingToEgg. + */ +class EggConverter : public EggFilter { +public: + EggConverter(const std::string &format_name, + const std::string &preferred_extension = std::string(), + bool allow_last_param = true, + bool allow_stdout = true); + +protected: + std::string _format_name; +}; + +#endif diff --git a/pandatool/src/eggbase/eggFilter.cxx b/pandatool/src/eggbase/eggFilter.cxx new file mode 100644 index 00000000..ef6c2967 --- /dev/null +++ b/pandatool/src/eggbase/eggFilter.cxx @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggFilter.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "eggFilter.h" + +/** + * + */ +EggFilter:: +EggFilter(bool allow_last_param, bool allow_stdout) : + EggWriter(allow_last_param, allow_stdout) +{ + // The default path store for programs that read egg files and write them + // again is PS_relative. + _path_replace->_path_store = PS_relative; + + clear_runlines(); + if (allow_last_param) { + add_runline("[opts] input.egg output.egg"); + } + add_runline("[opts] -o output.egg input.egg"); + if (allow_stdout) { + add_runline("[opts] input.egg >output.egg"); + } + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting egg file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the same coordinate system as the input egg file. If this is " + "different from the input egg file, a conversion will be performed."); +} + + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggFilter:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + if (!_got_path_directory && _got_output_filename) { + // Put in the name of the output directory. + _path_replace->_path_directory = _output_filename.get_dirname(); + } + + return EggReader::handle_args(args); +} + +/** + * + */ +bool EggFilter:: +post_command_line() { + // writer first, so we can fiddle with the _path_replace options if + // necessary. + return EggWriter::post_command_line() && EggReader::post_command_line(); +} diff --git a/pandatool/src/eggbase/eggFilter.h b/pandatool/src/eggbase/eggFilter.h new file mode 100644 index 00000000..ebd6c87d --- /dev/null +++ b/pandatool/src/eggbase/eggFilter.h @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggFilter.h + * @author drose + * @date 2000-02-14 + */ + +#ifndef EGGFILTER_H +#define EGGFILTER_H + +#include "pandatoolbase.h" + +#include "eggReader.h" +#include "eggWriter.h" + +/** + * This is the base class for a program that reads an egg file, operates on + * it, and writes another egg file out. + */ +class EggFilter : public EggReader, public EggWriter { +public: + EggFilter(bool allow_last_param = false, bool allow_stdout = true); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); +}; + +#endif diff --git a/pandatool/src/eggbase/eggMakeSomething.cxx b/pandatool/src/eggbase/eggMakeSomething.cxx new file mode 100644 index 00000000..52543ec0 --- /dev/null +++ b/pandatool/src/eggbase/eggMakeSomething.cxx @@ -0,0 +1,25 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeSomething.cxx + * @author drose + * @date 2003-10-01 + */ + +#include "eggMakeSomething.h" + +/** + * + */ +EggMakeSomething:: +EggMakeSomething() : + EggWriter(true, true) +{ + add_normals_options(); + add_transform_options(); +} diff --git a/pandatool/src/eggbase/eggMakeSomething.h b/pandatool/src/eggbase/eggMakeSomething.h new file mode 100644 index 00000000..b34aa348 --- /dev/null +++ b/pandatool/src/eggbase/eggMakeSomething.h @@ -0,0 +1,30 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeSomething.h + * @author drose + * @date 2003-10-01 + */ + +#ifndef EGGMAKESOMETHING_H +#define EGGMAKESOMETHING_H + +#include "pandatoolbase.h" + +#include "eggWriter.h" + +/** + * A base class for a family of programs that generate egg models of various + * fundamental shapes. + */ +class EggMakeSomething : public EggWriter { +public: + EggMakeSomething(); +}; + +#endif diff --git a/pandatool/src/eggbase/eggMultiBase.cxx b/pandatool/src/eggbase/eggMultiBase.cxx new file mode 100644 index 00000000..426b62a9 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiBase.cxx @@ -0,0 +1,187 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMultiBase.cxx + * @author drose + * @date 2000-11-02 + */ + +#include "eggMultiBase.h" +#include "eggBase.h" +#include "eggData.h" +#include "eggComment.h" +#include "filename.h" +#include "dSearchPath.h" + +/** + * + */ +EggMultiBase:: +EggMultiBase() { + add_option + ("f", "", 80, + "Force complete loading: load up the egg file along with all of its " + "external references.", + &EggMultiBase::dispatch_none, &_force_complete); + + add_option + ("noabs", "", 0, + "Don't allow any of the named egg files to have absolute pathnames. " + "If any do, abort with an error. This option is designed to help " + "detect errors when populating or building a standalone model tree, " + "which should be self-contained and include only relative pathnames.", + &EggMultiBase::dispatch_none, &_noabs); +} + +/** + * Performs any processing of the egg file(s) that is appropriate before + * writing them out. This includes any normal adjustments the user requested + * via -np, etc. + * + * Normally, you should not need to call this function directly; + * write_egg_files() calls it for you. You should call this only if you do + * not use write_egg_files() to write out the resulting egg files. + */ +void EggMultiBase:: +post_process_egg_files() { + if (_eggs.empty()) { + return; + } + + Eggs::iterator ei; + if (_got_transform) { + nout << "Applying transform matrix:\n"; + _transform.write(nout, 2); + LVecBase3d scale, hpr, translate; + if (decompose_matrix(_transform, scale, hpr, translate, + _eggs[0]->get_coordinate_system())) { + nout << "(scale " << scale << ", hpr " << hpr << ", translate " + << translate << ")\n"; + } + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->transform(_transform); + } + } + + if (_make_points) { + nout << "Making points\n"; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->make_point_primitives(); + } + } + + switch (_normals_mode) { + case NM_strip: + nout << "Stripping normals.\n"; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->strip_normals(); + (*ei)->remove_unused_vertices(true); + } + break; + + case NM_polygon: + nout << "Recomputing polygon normals.\n"; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->recompute_polygon_normals(); + (*ei)->remove_unused_vertices(true); + } + break; + + case NM_vertex: + nout << "Recomputing vertex normals.\n"; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->recompute_vertex_normals(_normals_threshold); + (*ei)->remove_unused_vertices(true); + } + break; + + case NM_preserve: + // Do nothing. + break; + } + + if (_got_tbnall) { + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + if ((*ei)->recompute_tangent_binormal(GlobPattern("*"))) { + (*ei)->remove_unused_vertices(true); + } + } + } else { + if (_got_tbnauto) { + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + if ((*ei)->recompute_tangent_binormal_auto()) { + (*ei)->remove_unused_vertices(true); + } + } + } + + for (vector_string::const_iterator si = _tbn_names.begin(); + si != _tbn_names.end(); + ++si) { + GlobPattern uv_name(*si); + nout << "Computing tangent and binormal for \"" << uv_name << "\"\n"; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + (*ei)->recompute_tangent_binormal(uv_name); + (*ei)->remove_unused_vertices(true); + } + } + } +} + + +/** + * Allocates and returns a new EggData structure that represents the indicated + * egg file. If the egg file cannot be read for some reason, returns NULL. + * + * This can be overridden by derived classes to control how the egg files are + * read, or to extend the information stored with each egg structure, by + * deriving from EggData. + */ +PT(EggData) EggMultiBase:: +read_egg(const Filename &filename) { + PT(EggData) data = new EggData; + + if (!data->read(filename)) { + // Failure reading. + return nullptr; + } + + if (_noabs && data->original_had_absolute_pathnames()) { + nout << filename.get_basename() + << " includes absolute pathnames!\n"; + return nullptr; + } + + DSearchPath file_path; + file_path.append_directory(filename.get_dirname()); + + // We always resolve filenames first based on the source egg filename, since + // egg files almost always store relative paths. This is a temporary kludge + // around integrating the path_replace system with the EggData better. + // Update: I believe this kludge is obsolete. Commenting out. - Josh. + // data->resolve_filenames(file_path); + + if (_force_complete) { + if (!data->load_externals()) { + return nullptr; + } + } + + // Now resolve the filenames again according to the user's specified + // _path_replace. + EggBase::convert_paths(data, _path_replace, file_path); + + if (_got_coordinate_system) { + data->set_coordinate_system(_coordinate_system); + } else { + _coordinate_system = data->get_coordinate_system(); + _got_coordinate_system = true; + } + + return data; +} diff --git a/pandatool/src/eggbase/eggMultiBase.h b/pandatool/src/eggbase/eggMultiBase.h new file mode 100644 index 00000000..1442de61 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiBase.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMultiBase.h + * @author drose + * @date 2000-11-02 + */ + +#ifndef EGGMULTIBASE_H +#define EGGMULTIBASE_H + +#include "pandatoolbase.h" + +#include "eggBase.h" +#include "coordinateSystem.h" +#include "eggData.h" +#include "pointerTo.h" + +class Filename; + +/** + * This specialization of ProgramBase is intended for programs that read + * and/or write multiple egg files. + * + * See also EggMultiFilter, for a class that also knows how to read a bunch of + * egg files in and write them out again. + */ +class EggMultiBase : public EggBase { +public: + EggMultiBase(); + + void post_process_egg_files(); + +protected: + virtual PT(EggData) read_egg(const Filename &filename); + +protected: + typedef pvector< PT(EggData) > Eggs; + Eggs _eggs; + + bool _force_complete; +}; + +#endif diff --git a/pandatool/src/eggbase/eggMultiFilter.cxx b/pandatool/src/eggbase/eggMultiFilter.cxx new file mode 100644 index 00000000..981c8ad4 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiFilter.cxx @@ -0,0 +1,210 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMultiFilter.cxx + * @author drose + * @date 2000-11-02 + */ + +#include "eggMultiFilter.h" + +#include "pnotify.h" +#include "eggData.h" + +/** + * + */ +EggMultiFilter:: +EggMultiFilter(bool allow_empty) : _allow_empty(allow_empty) { + clear_runlines(); + add_runline("-o output.egg [opts] input.egg"); + add_runline("-d dirname [opts] file.egg [file.egg ...]"); + add_runline("-inplace [opts] file.egg [file.egg ...]"); + add_runline("-inf input_list_filename [opts]"); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting egg file will be written. " + "This is only valid when there is only one input egg file on the command " + "line. If you want to process multiple files simultaneously, you must " + "use either -d or -inplace.", + &EggMultiFilter::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("d", "dirname", 50, + "Specify the name of the directory in which to write the resulting egg " + "files. If you are processing only one egg file, this may be omitted " + "in lieu of the -o option. If you are processing multiple egg files, " + "this may be omitted only if you specify -inplace instead.", + &EggMultiFilter::dispatch_filename, &_got_output_dirname, &_output_dirname); + + add_option + ("inplace", "", 50, + "If this option is given, the input egg files will be rewritten in " + "place with the results. This obviates the need to specify -d " + "for an output directory; however, it's risky because the original " + "input egg files are lost.", + &EggMultiFilter::dispatch_none, &_inplace); + + add_option + ("inf", "filename", 95, + "Reads input args from a text file instead of the command line. " + "Useful for really, really large lists of args that break the " + "OS-imposed limits on the length of command lines.", + &EggMultiFilter::dispatch_filename, &_got_input_filename, &_input_filename); + + // Derived programs will set this true when they discover some command-line + // option that will prevent the program from generating output. This + // removes some checks for an output specification in handle_args. + _read_only = false; +} + + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggMultiFilter:: +handle_args(ProgramBase::Args &args) { + if (_got_input_filename) { + nout << "Populating args from input file: " << _input_filename << "\n"; + // Makes sure the file is set is_text + _filename = Filename::text_filename(_input_filename); + std::ifstream input; + if (!_filename.open_read(input)) { + nout << "Error opening file: " << _input_filename << "\n"; + return false; + } + std::string line; + // File should be a space-delimited list of egg files + while (std::getline(input, line, ' ')) { + args.push_back(line); + } + } + if (args.empty()) { + if (!_allow_empty) { + nout << "You must specify the egg file(s) to read on the command line.\n"; + return false; + } + } else { + // These only apply if we have specified any egg files. + if (_got_output_filename && args.size() == 1) { + if (_got_output_dirname) { + nout << "Cannot specify both -o and -d.\n"; + return false; + } else if (_inplace) { + nout << "Cannot specify both -o and -inplace.\n"; + return false; + } + + } else { + if (_got_output_filename) { + nout << "Cannot use -o when multiple egg files are specified.\n"; + return false; + } + + if (_got_output_dirname && _inplace) { + nout << "Cannot specify both -inplace and -d.\n"; + return false; + + } else if (!_got_output_dirname && !_inplace) { + if (!_read_only) { + nout << "You must specify either -inplace or -d.\n"; + return false; + } + } + } + } + + // We need to set up _path_replace before we call read_egg(). + if (!_got_path_directory) { + // Put in the name of the output directory. + if (_got_output_filename) { + _path_replace->_path_directory = _output_filename.get_dirname(); + } else if (_got_output_dirname) { + _path_replace->_path_directory = _output_dirname; + } + } + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + PT(EggData) data = read_egg(Filename::from_os_specific(*ai)); + if (data == nullptr) { + // Rather than returning false, we simply exit here, so the ProgramBase + // won't try to tell the user how to run the program just because we got + // a bad egg file. + exit(1); + } + + _eggs.push_back(data); + } + + return true; +} + +/** + * + */ +bool EggMultiFilter:: +post_command_line() { + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *data = (*ei); + if (_got_coordinate_system) { + data->set_coordinate_system(_coordinate_system); + } + append_command_comment(data); + } + + return EggMultiBase::post_command_line(); +} + +/** + * Returns the output filename of the egg file with the given input filename. + * This is based on the user's choice of -inplace, -o, or -d. + */ +Filename EggMultiFilter:: +get_output_filename(const Filename &source_filename) const { + if (_got_output_filename) { + nassertr(!_inplace && !_got_output_dirname && _eggs.size() == 1, Filename()); + return _output_filename; + + } else if (_got_output_dirname) { + nassertr(!_inplace, Filename()); + Filename result = source_filename; + result.set_dirname(_output_dirname); + return result; + } + + nassertr(_inplace, Filename()); + return source_filename; +} + +/** + * Writes out all of the egg files in the _eggs vector, to the output + * directory if one is specified, or over the input files if -inplace was + * specified. + */ +void EggMultiFilter:: +write_eggs() { + nassertv(!_read_only); + post_process_egg_files(); + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *data = (*ei); + Filename filename = get_output_filename(data->get_egg_filename()); + + nout << "Writing " << filename << "\n"; + filename.make_dir(); + if (!data->write_egg(filename)) { + // Error writing an egg file; abort. + exit(1); + } + } +} diff --git a/pandatool/src/eggbase/eggMultiFilter.h b/pandatool/src/eggbase/eggMultiFilter.h new file mode 100644 index 00000000..402e5183 --- /dev/null +++ b/pandatool/src/eggbase/eggMultiFilter.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMultiFilter.h + * @author drose + * @date 2000-11-02 + */ + +#ifndef EGGMULTIFILTER_H +#define EGGMULTIFILTER_H + +#include "pandatoolbase.h" + +#include "eggMultiBase.h" + +/** + * This is a base class for a program that reads in a number of egg files, + * operates on them, and writes them out again (presumably to a different + * directory). + */ +class EggMultiFilter : public EggMultiBase { +public: + EggMultiFilter(bool allow_empty = false); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + Filename get_output_filename(const Filename &source_filename) const; + virtual void write_eggs(); + +protected: + bool _allow_empty; + bool _got_output_filename; + Filename _output_filename; + bool _got_output_dirname; + Filename _output_dirname; + bool _inplace; + Filename _input_filename; + Filename _filename; + bool _got_input_filename; + + bool _read_only; +}; + +#endif diff --git a/pandatool/src/eggbase/eggReader.cxx b/pandatool/src/eggbase/eggReader.cxx new file mode 100644 index 00000000..4146189a --- /dev/null +++ b/pandatool/src/eggbase/eggReader.cxx @@ -0,0 +1,375 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggReader.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "eggReader.h" + +#include "pnmImage.h" +#include "config_putil.h" +#include "eggTextureCollection.h" +#include "eggGroup.h" +#include "eggGroupNode.h" +#include "eggSwitchCondition.h" +#include "string_utils.h" +#include "dcast.h" + +/** + * + */ +EggReader:: +EggReader() { + clear_runlines(); + add_runline("[opts] input.egg"); + + redescribe_option + ("cs", + "Specify the coordinate system to operate in. This may be " + " one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the coordinate system of the input egg file."); + + add_option + ("f", "", 80, + "Force complete loading: load up the egg file along with all of its " + "external references.", + &EggReader::dispatch_none, &_force_complete); + + add_option + ("noabs", "", 0, + "Don't allow the input egg file to have absolute pathnames. " + "If it does, abort with an error. This option is designed to help " + "detect errors when populating or building a standalone model tree, " + "which should be self-contained and include only relative pathnames.", + &EggReader::dispatch_none, &_noabs); + + _tex_type = nullptr; + _delod = -1.0; + + _got_tex_dirname = false; + _got_tex_extension = false; + _exit_on_failure = true; +} + +/** + * Adds -td, -te, etc. as valid options for this program. If the user + * specifies one of the options on the command line, the textures will be + * copied and converted as each egg file is read. + * + * Note that if you call this function to add these options, you must call + * do_reader_options() at the appropriate point before or during processing to + * execute the options if the user specified them. + */ +void EggReader:: +add_texture_options() { + add_option + ("td", "dirname", 40, + "Copy textures to the indicated directory. The copy is performed " + "only if the destination file does not exist or is older than the " + "source file.", + &EggReader::dispatch_filename, &_got_tex_dirname, &_tex_dirname); + + add_option + ("te", "ext", 40, + "Rename textures to have the indicated extension. This also " + "automatically copies them to the new filename (possibly in a " + "different directory if -td is also specified), and may implicitly " + "convert to a different image format according to the extension.", + &EggReader::dispatch_string, &_got_tex_extension, &_tex_extension); + + add_option + ("tt", "type", 40, + "Explicitly specifies the image format to convert textures to " + "when copying them via -td or -te. Normally, this is unnecessary as " + "the image format can be determined by the extension, but sometimes " + "the extension is insufficient to unambiguously specify an image " + "type.", + &EggReader::dispatch_image_type, nullptr, &_tex_type); +} + +/** + * Adds -delod as a valid option for this program. + * + * Note that if you call this function to add these options, you must call + * do_reader_options() at the appropriate point before or during processing to + * execute the options if the user specified them. + */ +void EggReader:: +add_delod_options(double default_delod) { + _delod = default_delod; + + if (default_delod < 0) { + add_option + ("delod", "dist", 40, + "Eliminate LOD's by choosing the level that would be appropriate for " + "a camera at the indicated fixed distance from each LOD. " + "Use -delod -1 to keep all the LOD's as they are, which is " + "the default.\n", + &EggReader::dispatch_double, nullptr, &_delod); + + } else { + add_option + ("delod", "dist", 40, + "Eliminate LOD's by choosing the level that would be appropriate for " + "a camera at the indicated fixed distance from each LOD. " + "Use -delod -1 to keep all the LOD's as they are. The default value " + "is " + format_string(default_delod) + ".", + &EggReader::dispatch_double, nullptr, &_delod); + } +} + +/** + * Sets whether the reader will quit the program upon encountering a fatal error. + * + * If true, the entire program will quit as soon as an egg loading error occurs. + */ +void EggReader:: +set_exit_on_failure(bool exit_on_failure) { + _exit_on_failure = exit_on_failure; +} + +/** + * Returns this object as an EggReader pointer, if it is in fact an EggReader, + * or NULL if it is not. + * + * This is intended to work around the C++ limitation that prevents downcasts + * past virtual inheritance. Since both EggReader and EggWriter inherit + * virtually from EggSingleBase, we need functions like this to downcast to + * the appropriate pointer. + */ +EggReader *EggReader:: +as_reader() { + return this; +} + +/** + * Performs any processing of the egg file that is appropriate after reading + * it in. + * + * Normally, you should not need to call this function directly; it is called + * automatically at startup. + */ +void EggReader:: +pre_process_egg_file() { +} + +/** + * + */ +bool EggReader:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the egg file(s) to read on the command line.\n"; + return false; + } + + // Any separate egg files that are listed on the command line will get + // implicitly loaded up into one big egg file. + + if (!args.empty()) { + _data->set_egg_filename(Filename::from_os_specific(args[0])); + } + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + Filename filename = Filename::from_os_specific(*ai); + + EggData file_data; + if (filename != "-") { + if (!file_data.read(filename)) { + // Rather than returning false, we simply exit here, so the ProgramBase + // won't try to tell the user how to run the program just because we got + // a bad egg file. + if (_exit_on_failure) { + exit(1); + } + return false; + } + } else { + if (!file_data.read(std::cin)) { + if (_exit_on_failure) { + exit(1); + } + return false; + } + } + + if (_noabs && file_data.original_had_absolute_pathnames()) { + nout << filename.get_basename() + << " includes absolute pathnames!\n"; + if (_exit_on_failure) { + exit(1); + } + return false; + } + + DSearchPath file_path; + file_path.append_directory(filename.get_dirname()); + + if (_force_complete) { + if (!file_data.load_externals(file_path)) { + if (_exit_on_failure) { + exit(1); + } + return false; + } + } + + // Now resolve the filenames again according to the user's specified + // _path_replace. + convert_paths(&file_data, _path_replace, file_path); + + _data->merge(file_data); + } + + pre_process_egg_file(); + + return true; +} + +/** + * This is called after the command line has been completely processed, and it + * gives the program a chance to do some last-minute processing and validation + * of the options and arguments. It should return true if everything is fine, + * false if there is an error. + */ +bool EggReader:: +post_command_line() { + return EggSingleBase::post_command_line(); +} + +/** + * Postprocesses the egg file as the user requested according to whatever + * command-line options are in effect. Returns true if everything is done + * correctly, false if there was some problem. + */ +bool EggReader:: +do_reader_options() { + bool okflag = true; + + if (_got_tex_dirname || _got_tex_extension) { + if (!copy_textures()) { + okflag = false; + } + } + + if (_delod >= 0.0) { + do_delod(_data); + } + + return okflag; +} + +/** + * Renames and copies the textures referenced in the egg file, if so specified + * by the -td and -te options. Returns true if all textures are copied + * successfully, false if any one of them failed. + */ +bool EggReader:: +copy_textures() { + bool success = true; + EggTextureCollection textures; + textures.find_used_textures(_data); + + EggTextureCollection::const_iterator ti; + for (ti = textures.begin(); ti != textures.end(); ++ti) { + EggTexture *tex = (*ti); + Filename orig_filename = tex->get_filename(); + if (!orig_filename.exists()) { + bool found = orig_filename.resolve_filename(get_model_path()); + if (!found) { + nout << "Cannot find " << orig_filename << "\n"; + success = false; + continue; + } + } + + Filename new_filename = orig_filename; + if (_got_tex_dirname) { + new_filename.set_dirname(_tex_dirname); + } + if (_got_tex_extension) { + new_filename.set_extension(_tex_extension); + } + + if (orig_filename != new_filename) { + tex->set_filename(new_filename); + + // The new filename is different; does it need copying? + int compare = + orig_filename.compare_timestamps(new_filename, true, true); + if (compare > 0) { + // Yes, it does. Copy it! + nout << "Reading " << orig_filename << "\n"; + PNMImage image; + if (!image.read(orig_filename)) { + nout << " unable to read!\n"; + success = false; + } else { + nout << "Writing " << new_filename << "\n"; + if (!image.write(new_filename, _tex_type)) { + nout << " unable to write!\n"; + success = false; + } + } + } + } + } + + return success; +} + +/** + * Removes all the LOD's in the egg file by treating the camera as being + * _delod distance from each LOD. Returns true if this particular group should + * be preserved, false if it should be removed. + */ +bool EggReader:: +do_delod(EggNode *node) { + if (node->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, node); + if (group->has_lod()) { + const EggSwitchCondition &cond = group->get_lod(); + if (cond.is_of_type(EggSwitchConditionDistance::get_class_type())) { + const EggSwitchConditionDistance *dist = + DCAST(EggSwitchConditionDistance, &cond); + if (_delod >= dist->_switch_out && _delod < dist->_switch_in) { + // Preserve this group node, but not the LOD information itself. + nout << "Preserving LOD " << node->get_name() + << " (" << dist->_switch_out << " to " << dist->_switch_in + << ")\n"; + group->clear_lod(); + } else { + // Remove this group node. + nout << "Eliminating LOD " << node->get_name() + << " (" << dist->_switch_out << " to " << dist->_switch_in + << ")\n"; + return false; + } + } + } + } + + // Now process all the children. + if (node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, node); + EggGroupNode::iterator ci; + ci = group->begin(); + while (ci != group->end()) { + EggNode *child = *ci; + ++ci; + + if (!do_delod(child)) { + group->remove_child(child); + } + } + } + + return true; +} diff --git a/pandatool/src/eggbase/eggReader.h b/pandatool/src/eggbase/eggReader.h new file mode 100644 index 00000000..305b58a2 --- /dev/null +++ b/pandatool/src/eggbase/eggReader.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggReader.h + * @author drose + * @date 2000-02-14 + */ + +#ifndef EGGREADER_H +#define EGGREADER_H + +#include "pandatoolbase.h" + +#include "eggSingleBase.h" +#include "filename.h" + +class PNMFileType; + +/** + * This is the base class for a program that reads egg files, but doesn't + * write an egg file. + */ +class EggReader : virtual public EggSingleBase { +public: + EggReader(); + + void add_texture_options(); + void add_delod_options(double default_delod = -1.0); + void set_exit_on_failure(bool exit_on_failure); + + virtual EggReader *as_reader(); + virtual void pre_process_egg_file(); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + bool do_reader_options(); + +private: + bool copy_textures(); + bool do_delod(EggNode *node); + +protected: + bool _force_complete; + bool _exit_on_failure; + +private: + Filename _tex_dirname; + bool _got_tex_dirname; + std::string _tex_extension; + bool _got_tex_extension; + PNMFileType *_tex_type; + double _delod; +}; + +#endif diff --git a/pandatool/src/eggbase/eggSingleBase.cxx b/pandatool/src/eggbase/eggSingleBase.cxx new file mode 100644 index 00000000..a3cda117 --- /dev/null +++ b/pandatool/src/eggbase/eggSingleBase.cxx @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSingleBase.cxx + * @author drose + * @date 2003-07-21 + */ + +#include "eggSingleBase.h" + +#include "eggGroupNode.h" +#include "eggTexture.h" +#include "eggFilenameNode.h" +#include "eggComment.h" +#include "dcast.h" +#include "string_utils.h" + +/** + * + */ +EggSingleBase:: +EggSingleBase() : + _data(new EggData) +{ +} + +/** + * Returns this object as an EggReader pointer, if it is in fact an EggReader, + * or NULL if it is not. + * + * This is intended to work around the C++ limitation that prevents downcasts + * past virtual inheritance. Since both EggReader and EggWriter inherit + * virtually from EggSingleBase, we need functions like this to downcast to + * the appropriate pointer. + */ +EggReader *EggSingleBase:: +as_reader() { + return nullptr; +} + +/** + * Returns this object as an EggWriter pointer, if it is in fact an EggWriter, + * or NULL if it is not. + * + * This is intended to work around the C++ limitation that prevents downcasts + * past virtual inheritance. Since both EggReader and EggWriter inherit + * virtually from EggSingleBase, we need functions like this to downcast to + * the appropriate pointer. + */ +EggWriter *EggSingleBase:: +as_writer() { + return nullptr; +} + +/** + * + */ +bool EggSingleBase:: +post_command_line() { + if (_got_coordinate_system) { + _data->set_coordinate_system(_coordinate_system); + } + + return EggBase::post_command_line(); +} diff --git a/pandatool/src/eggbase/eggSingleBase.h b/pandatool/src/eggbase/eggSingleBase.h new file mode 100644 index 00000000..32c15d83 --- /dev/null +++ b/pandatool/src/eggbase/eggSingleBase.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSingleBase.h + * @author drose + * @date 2003-07-21 + */ + +#ifndef EGGSINGLEBASE_H +#define EGGSINGLEBASE_H + +#include "pandatoolbase.h" + +#include "eggBase.h" +#include "coordinateSystem.h" +#include "eggData.h" +#include "pointerTo.h" + +class EggReader; +class EggWriter; +class EggNode; +class PathReplace; + +/** + * This specialization of EggBase is intended for programs that read and/or + * write a single egg file. (See EggMultiBase for programs that operate on + * multiple egg files at once.) + * + * This is just a base class; see EggReader, EggWriter, or EggFilter according + * to your particular I/O needs. + */ +class EggSingleBase : public EggBase { +public: + EggSingleBase(); + + virtual EggReader *as_reader(); + virtual EggWriter *as_writer(); + +protected: + virtual bool post_command_line(); + +protected: + PT(EggData) _data; +}; + +#endif diff --git a/pandatool/src/eggbase/eggToSomething.cxx b/pandatool/src/eggbase/eggToSomething.cxx new file mode 100644 index 00000000..eb6adc89 --- /dev/null +++ b/pandatool/src/eggbase/eggToSomething.cxx @@ -0,0 +1,155 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToSomething.cxx + * @author drose + * @date 2000-02-15 + */ + +#include "eggToSomething.h" + +/** + * The first parameter to the constructor should be the one-word name of the + * file format that is to be read, for instance "OpenFlight" or "Alias". It's + * just used in printing error messages and such. + */ +EggToSomething:: +EggToSomething(const std::string &format_name, + const std::string &preferred_extension, + bool allow_last_param, bool allow_stdout) : + EggConverter(format_name, preferred_extension, allow_last_param, + allow_stdout) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] input.egg output" + _preferred_extension); + } + add_runline("[opts] -o output" + _preferred_extension + " input.egg"); + if (_allow_stdout) { + add_runline("[opts] input.egg >output" + _preferred_extension); + } + + std::string o_description; + + if (_allow_stdout) { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file, or standard output is used if there are no " + "other parameters."; + } else { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the " + format_name + + " file is written to standard output."; + } + } else { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file."; + } else { + o_description = + "Specify the filename to which the resulting " + format_name + + " file will be written."; + } + } + + redescribe_option("o", o_description); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting " + _format_name + + " file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default " + "is the same coordinate system as the input egg file. If this is " + "different from the input egg file, a conversion will be performed."); + + _input_units = DU_invalid; + _output_units = DU_invalid; +} + +/** + * Adds -ui and -uo as valid options for this program. If the user specifies + * -uo and -ui, or just -uo and the program specifies -ui by setting + * _input_units, the indicated units conversion will be automatically applied + * before writing out the egg file. + */ +void EggToSomething:: +add_units_options() { + add_option + ("ui", "units", 40, + "Specify the units of the input egg file. If this is " + "specified, the vertices in the egg file will be scaled as " + "necessary to make the appropriate units conversion; otherwise, " + "the vertices will be left as they are.", + &EggToSomething::dispatch_units, nullptr, &_input_units); + + add_option + ("uo", "units", 40, + "Specify the units of the resulting " + _format_name + + " file. Normally, the default units for the format are used.", + &EggToSomething::dispatch_units, nullptr, &_output_units); +} + +/** + * Applies the scale indicated by the input and output units to the indicated + * egg file. This is normally done automatically when the file is read in. + */ +void EggToSomething:: +apply_units_scale(EggData *data) { + + // [gjeon] since maya's internal unit is fixed to cm and when we can't + // change UI unit without affecting data we need to convert data to cm for + // now this will be set later to proper output unit user provided by using + // MayaApi::set_units() in eggToMaya.cxx + DistanceUnit output_units = _output_units; + if (_format_name == "Maya") + _output_units = DU_centimeters; + + if (_output_units != DU_invalid && _input_units != DU_invalid && + _input_units != _output_units) { + nout << "Converting from " << format_long_unit(_input_units) + << " to " << format_long_unit(_output_units) << "\n"; + double scale = convert_units(_input_units, _output_units); + data->transform(LMatrix4d::scale_mat(scale)); + } + _output_units = output_units; +} + +/** + * Performs any processing of the egg file that is appropriate after reading + * it in. + * + * Normally, you should not need to call this function directly; it is called + * automatically at startup. + */ +void EggToSomething:: +pre_process_egg_file() { + apply_units_scale(_data); + EggConverter::pre_process_egg_file(); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggToSomething:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + return EggConverter::handle_args(args); +} diff --git a/pandatool/src/eggbase/eggToSomething.h b/pandatool/src/eggbase/eggToSomething.h new file mode 100644 index 00000000..aafc045a --- /dev/null +++ b/pandatool/src/eggbase/eggToSomething.h @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToSomething.h + * @author drose + * @date 2000-02-15 + */ + +#ifndef EGGTOSOMETHING_H +#define EGGTOSOMETHING_H + +#include "pandatoolbase.h" + +#include "eggConverter.h" +#include "distanceUnit.h" + +/** + * This is the general base class for a file-converter program that reads some + * model file format and generates an egg file. + */ +class EggToSomething : public EggConverter { +public: + EggToSomething(const std::string &format_name, + const std::string &preferred_extension = std::string(), + bool allow_last_param = true, + bool allow_stdout = true); + + void add_units_options(); + +protected: + void apply_units_scale(EggData *data); + virtual void pre_process_egg_file(); + virtual bool handle_args(Args &args); + + DistanceUnit _input_units; + DistanceUnit _output_units; +}; + +#endif diff --git a/pandatool/src/eggbase/eggWriter.cxx b/pandatool/src/eggbase/eggWriter.cxx new file mode 100644 index 00000000..c881991a --- /dev/null +++ b/pandatool/src/eggbase/eggWriter.cxx @@ -0,0 +1,224 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggWriter.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "eggWriter.h" + +#include "string_utils.h" +#include "compose_matrix.h" +#include "globPattern.h" + +/** + * Egg-writing type programs may specify their output file using either the + * last-filename convention, the -o convention, and/or implicitly writing the + * result to standard output. Not all interfaces are appropriate for all + * applications; some may be confusing or dangerous. + * + * The calling application should pass allow_last_param true to allow the user + * to specify the output filename as the last parameter on the command line + * (the most dangerous, but convenient, method), and allow_stdout true to + * allow the user to omit the output filename altogether and have the output + * implicitly go to standard output (not terribly dangerous, but inappropriate + * when writing binary file formats). + */ +EggWriter:: +EggWriter(bool allow_last_param, bool allow_stdout) : + WithOutputFile(allow_last_param, allow_stdout, false) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = ".egg"; + + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] output.egg"); + } + add_runline("[opts] -o output.egg"); + if (_allow_stdout) { + add_runline("[opts] >output.egg"); + } + + std::string o_description; + + if (_allow_stdout) { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file, or standard output is used if there are no " + "other parameters."; + } else { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the egg file is written to standard output."; + } + } else { + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting egg file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file."; + } else { + o_description = + "Specify the filename to which the resulting egg file will be written."; + } + } + + add_option + ("o", "filename", 50, o_description, + &EggWriter::dispatch_filename, &_got_output_filename, &_output_filename); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting egg file. This may be " + "one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default is " + "y-up."); +} + + +/** + * Returns this object as an EggWriter pointer, if it is in fact an EggWriter, + * or NULL if it is not. + * + * This is intended to work around the C++ limitation that prevents downcasts + * past virtual inheritance. Since both EggReader and EggWriter inherit + * virtually from EggSingleBase, we need functions like this to downcast to + * the appropriate pointer. + */ +EggWriter *EggWriter:: +as_writer() { + return this; +} + +/** + * Performs any processing of the egg file that is appropriate before writing + * it out. This includes any normal adjustments the user requested via -np, + * etc. + * + * Normally, you should not need to call this function directly; + * write_egg_file() calls it for you. You should call this only if you do not + * use write_egg_file() to write out the resulting egg file. + */ +void EggWriter:: +post_process_egg_file() { + if (_got_transform) { + nout << "Applying transform matrix:\n"; + _transform.write(nout, 2); + LVecBase3d scale, hpr, translate; + if (decompose_matrix(_transform, scale, hpr, translate, + _data->get_coordinate_system())) { + nout << "(scale " << scale << ", hpr " << hpr << ", translate " + << translate << ")\n"; + } + _data->transform(_transform); + } + + if (_make_points) { + nout << "Making points\n"; + _data->make_point_primitives(); + } + + bool needs_remove = false; + + switch (_normals_mode) { + case NM_strip: + nout << "Stripping normals.\n"; + _data->strip_normals(); + needs_remove = true; + break; + + case NM_polygon: + nout << "Recomputing polygon normals.\n"; + _data->recompute_polygon_normals(); + needs_remove = true; + break; + + case NM_vertex: + nout << "Recomputing vertex normals.\n"; + _data->recompute_vertex_normals(_normals_threshold); + needs_remove = true; + break; + + case NM_preserve: + // Do nothing. + break; + } + + if (_got_tbnall) { + needs_remove |= _data->recompute_tangent_binormal(GlobPattern("*")); + } else { + if (_got_tbnauto) { + needs_remove |= _data->recompute_tangent_binormal_auto(); + } + needs_remove |= _data->recompute_tangent_binormal(_tbn_names); + } + + if (needs_remove) { + _data->remove_unused_vertices(true); + } +} + +/** + * Writes out the egg file as the normal result of the program. This calls + * post_process_egg_file() to perform any last minute processing (like normal + * computation) and then writes out the file to the output stream returned by + * get_output(). + */ +void EggWriter:: +write_egg_file() { + post_process_egg_file(); + _data->write_egg(get_output()); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggWriter:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 0)) { + return false; + } + + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + nout << (*ai) << " "; + } + nout << "\r"; + return false; + } + + if (!_got_path_directory && _got_output_filename) { + // Put in the name of the output directory. + _path_replace->_path_directory = _output_filename.get_dirname(); + } + + return true; +} + +/** + * + */ +bool EggWriter:: +post_command_line() { + if (!_allow_stdout && !_got_output_filename) { + nout << "You must specify the filename to write with -o.\n"; + return false; + } + + append_command_comment(_data); + + return EggSingleBase::post_command_line(); +} diff --git a/pandatool/src/eggbase/eggWriter.h b/pandatool/src/eggbase/eggWriter.h new file mode 100644 index 00000000..9037fe7e --- /dev/null +++ b/pandatool/src/eggbase/eggWriter.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggWriter.h + * @author drose + * @date 2000-02-14 + */ + +#ifndef EGGWRITER_H +#define EGGWRITER_H + +#include "pandatoolbase.h" +#include "eggSingleBase.h" +#include "withOutputFile.h" + +#include "filename.h" +#include "luse.h" + +/** + * This is the base class for a program that generates an egg file output, but + * doesn't read any for input. + */ +class EggWriter : virtual public EggSingleBase, public WithOutputFile { +public: + EggWriter(bool allow_last_param = false, bool allow_stdout = true); + + virtual EggWriter *as_writer(); + + virtual void post_process_egg_file(); + void write_egg_file(); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + +private: + std::ofstream _output_stream; +}; + +#endif diff --git a/pandatool/src/eggbase/p3eggbase_composite1.cxx b/pandatool/src/eggbase/p3eggbase_composite1.cxx new file mode 100644 index 00000000..7f2f3a72 --- /dev/null +++ b/pandatool/src/eggbase/p3eggbase_composite1.cxx @@ -0,0 +1,13 @@ + +#include "eggBase.cxx" +#include "eggConverter.cxx" +#include "eggFilter.cxx" +#include "eggMakeSomething.cxx" +#include "eggMultiBase.cxx" +#include "eggMultiFilter.cxx" +#include "eggReader.cxx" +#include "eggWriter.cxx" +#include "eggSingleBase.cxx" +#include "eggToSomething.cxx" +#include "somethingToEgg.cxx" + diff --git a/pandatool/src/eggbase/somethingToEgg.cxx b/pandatool/src/eggbase/somethingToEgg.cxx new file mode 100644 index 00000000..45d705f1 --- /dev/null +++ b/pandatool/src/eggbase/somethingToEgg.cxx @@ -0,0 +1,328 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file somethingToEgg.cxx + * @author drose + * @date 2000-02-15 + */ + +#include "somethingToEgg.h" +#include "somethingToEggConverter.h" + +#include "config_putil.h" + +/** + * The first parameter to the constructor should be the one-word name of the + * file format that is to be read, for instance "OpenFlight" or "Alias". It's + * just used in printing error messages and such. + */ +SomethingToEgg:: +SomethingToEgg(const std::string &format_name, + const std::string &preferred_extension, + bool allow_last_param, bool allow_stdout) : + EggConverter(format_name, preferred_extension, allow_last_param, allow_stdout) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] input" + _preferred_extension + " output.egg"); + } + add_runline("[opts] -o output.egg input" + _preferred_extension); + if (_allow_stdout) { + add_runline("[opts] input" + _preferred_extension + " >output.egg"); + } + + // -f doesn't make sense if we aren't reading egg files. + remove_option("f"); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this can inferred from the file itself."); + + add_option + ("noabs", "", 0, + "Don't allow the input " + _format_name + " file to have absolute pathnames. " + "If it does, abort with an error. This option is designed to help " + "detect errors when populating or building a standalone model tree, " + "which should be self-contained and include only relative pathnames.", + &SomethingToEgg::dispatch_none, &_noabs); + + add_option + ("noexist", "", 0, + "Don't treat it as an error if the input file references pathnames " + "(e.g. textures) that don't exist. Normally, this will be flagged as " + "an error and the command aborted; with this option, an egg file will " + "be generated anyway, referencing pathnames that do not exist.", + &SomethingToEgg::dispatch_none, &_noexist); + + add_option + ("ignore", "", 0, + "Ignore non-fatal errors and generate an egg file anyway.", + &SomethingToEgg::dispatch_none, &_allow_errors); + + _input_units = DU_invalid; + _output_units = DU_invalid; + _animation_convert = AC_none; + _got_start_frame = false; + _got_end_frame = false; + _got_frame_inc = false; + _got_neutral_frame = false; + _got_input_frame_rate = false; + _got_output_frame_rate = false; + _merge_externals = false; +} + +/** + * Adds -ui and -uo as valid options for this program. If the user specifies + * -uo and -ui, or just -uo and the program specifies -ui by setting + * _input_units, the indicated units conversion will be automatically applied + * before writing out the egg file. + */ +void SomethingToEgg:: +add_units_options() { + add_option + ("ui", "units", 40, + "Specify the units of the input " + _format_name + + " file. Normally, this can be inferred from the file itself.", + &SomethingToEgg::dispatch_units, nullptr, &_input_units); + + add_option + ("uo", "units", 40, + "Specify the units of the resulting egg file. If this is " + "specified, the vertices in the egg file will be scaled as " + "necessary to make the appropriate units conversion; otherwise, " + "the vertices will be left as they are.", + &SomethingToEgg::dispatch_units, nullptr, &_output_units); +} + +/** + * Adds options appropriate to animation packages. + */ +void SomethingToEgg:: +add_animation_options() { + add_option + ("a", "animation-mode", 40, + "Specifies how animation from the " + _format_name + " file is " + "converted to egg, if at all. At present, the following keywords " + "are supported: none, pose, flip, strobe, model, chan, or both. " + "The default is none, which means not to convert animation.", + &SomethingToEgg::dispatch_animation_convert, nullptr, &_animation_convert); + + add_option + ("cn", "name", 40, + "Specifies the name of the animation character. This should match " + "between all of the model files and all of the channel files for a " + "particular model and its associated channels.", + &SomethingToEgg::dispatch_string, nullptr, &_character_name); + + add_option + ("sf", "start-frame", 40, + "Specifies the starting frame of animation to extract. If omitted, " + "the first frame of the time slider will be used. For -a pose, this " + "is the one frame of animation to extract.", + &SomethingToEgg::dispatch_double, &_got_start_frame, &_start_frame); + + add_option + ("ef", "end-frame", 40, + "Specifies the ending frame of animation to extract. If omitted, " + "the last frame of the time slider will be used.", + &SomethingToEgg::dispatch_double, &_got_end_frame, &_end_frame); + + add_option + ("if", "frame-inc", 40, + "Specifies the increment between successive frames. If omitted, " + "this is taken from the time slider settings, or 1.0 if the time " + "slider does not specify.", + &SomethingToEgg::dispatch_double, &_got_frame_inc, &_frame_inc); + + add_option + ("nf", "neutral-frame", 40, + "Specifies the frame number to use for the neutral pose. The model " + "will be set to this frame before extracting out the neutral character. " + "If omitted, the current frame of the model is used. This is only " + "relevant for -a model or -a both.", + &SomethingToEgg::dispatch_double, &_got_neutral_frame, &_neutral_frame); + + add_option + ("fri", "fps", 40, + "Specify the frame rate (frames per second) of the input " + _format_name + + " file. Normally, this can be inferred from the file itself.", + &SomethingToEgg::dispatch_double, &_got_input_frame_rate, &_input_frame_rate); + + add_option + ("fro", "fps", 40, + "Specify the frame rate (frames per second) of the generated animation. " + "If this is specified, the animation speed is scaled by the appropriate " + "factor based on the frame rate of the input file (see -fri).", + &SomethingToEgg::dispatch_double, &_got_output_frame_rate, &_output_frame_rate); +} + +/** + * Adds -f. + */ +void SomethingToEgg:: +add_merge_externals_options() { + add_option + ("f", "", 40, + "Follow and convert all external references in the source file.", + &SomethingToEgg::dispatch_none, &_merge_externals); +} + +/** + * Applies the scale indicated by the input and output units to the indicated + * egg file. This is normally done automatically when the file is written + * out. + */ +void SomethingToEgg:: +apply_units_scale(EggData *data) { + if (_output_units != DU_invalid && _input_units != DU_invalid && + _input_units != _output_units) { + nout << "Converting from " << format_long_unit(_input_units) + << " to " << format_long_unit(_output_units) << "\n"; + double scale = convert_units(_input_units, _output_units); + data->transform(LMatrix4d::scale_mat(scale)); + } +} + +/** + * Copies the relevant parameters specified by the user on the command line + * (if add_path_replace_options(), add_path_store_options(), or + * add_animation_options() was used) to the converter. + */ +void SomethingToEgg:: +apply_parameters(SomethingToEggConverter &converter) { + _path_replace->_noabs = _noabs; + _path_replace->_exists = !_noexist; + converter.set_path_replace(_path_replace); + + converter.set_animation_convert(_animation_convert); + converter.set_character_name(_character_name); + if (_got_start_frame) { + converter.set_start_frame(_start_frame); + } + if (_got_end_frame) { + converter.set_end_frame(_end_frame); + } + if (_got_frame_inc) { + converter.set_frame_inc(_frame_inc); + } + if (_got_neutral_frame) { + converter.set_neutral_frame(_neutral_frame); + } + if (_got_input_frame_rate) { + converter.set_input_frame_rate(_input_frame_rate); + } + if (_got_output_frame_rate) { + converter.set_output_frame_rate(_output_frame_rate); + } +} + +/** + * + */ +bool SomethingToEgg:: +handle_args(Args &args) { + if (_allow_last_param && !_got_output_filename && args.size() > 1) { + _got_output_filename = true; + _output_filename = Filename::from_os_specific(args.back()); + args.pop_back(); + + if (!(_output_filename.get_extension() == "egg")) { + nout << "Output filename " << _output_filename + << " does not end in .egg. If this is really what you intended, " + "use the -o output_file syntax.\n"; + return false; + } + + if (!verify_output_file_safe()) { + return false; + } + } + + if (args.empty()) { + nout << "You must specify the " << _format_name + << " file to read on the command line.\n"; + return false; + } + + if (args.size() != 1) { + nout << "You may only specify one " << _format_name + << " file to read on the command line. " + << "You specified: "; + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + nout << (*ai) << " "; + } + nout << "\n"; + return false; + } + + _input_filename = Filename::from_os_specific(args[0]); + + if (!_input_filename.exists()) { + nout << "Cannot find input file " << _input_filename << "\n"; + return false; + } + + if (!_got_path_directory && _got_output_filename) { + // Put in the name of the output directory. + _path_replace->_path_directory = _output_filename.get_dirname(); + } + + return true; +} + +/** + * This is called after the command line has been completely processed, and it + * gives the program a chance to do some last-minute processing and validation + * of the options and arguments. It should return true if everything is fine, + * false if there is an error. + */ +bool SomethingToEgg:: +post_command_line() { + // Prepend the source filename to the model path. + ConfigVariableSearchPath &model_path = get_model_path(); + Filename directory = _input_filename.get_dirname(); + if (directory.empty()) { + directory = "."; + } + model_path.prepend_directory(directory); + + return EggConverter::post_command_line(); +} + +/** + * Performs any processing of the egg file that is appropriate before writing + * it out. This includes any normal adjustments the user requested via -np, + * etc. + * + * Normally, you should not need to call this function directly; + * write_egg_file() calls it for you. You should call this only if you do not + * use write_egg_file() to write out the resulting egg file. + */ +void SomethingToEgg:: +post_process_egg_file() { + apply_units_scale(_data); + EggConverter::post_process_egg_file(); +} + +/** + * Dispatch function to set the given animation convert mode according to the + * specified parameter. var is a pointer to an AnimationConvert variable. + */ +bool SomethingToEgg:: +dispatch_animation_convert(const std::string &opt, const std::string &arg, void *var) { + AnimationConvert *ip = (AnimationConvert *)var; + (*ip) = string_animation_convert(arg); + if ((*ip) == AC_invalid) { + nout << "Invalid keyword for -" << opt << ": " << arg << "\n"; + return false; + } + + return true; +} diff --git a/pandatool/src/eggbase/somethingToEgg.h b/pandatool/src/eggbase/somethingToEgg.h new file mode 100644 index 00000000..5c5427dc --- /dev/null +++ b/pandatool/src/eggbase/somethingToEgg.h @@ -0,0 +1,76 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file somethingToEgg.h + * @author drose + * @date 2000-02-15 + */ + +#ifndef SOMETHINGTOEGG_H +#define SOMETHINGTOEGG_H + +#include "pandatoolbase.h" + +#include "eggConverter.h" +#include "distanceUnit.h" +#include "animationConvert.h" + +class SomethingToEggConverter; + +/** + * This is the general base class for a file-converter program that reads some + * model file format and generates an egg file. + */ +class SomethingToEgg : public EggConverter { +public: + SomethingToEgg(const std::string &format_name, + const std::string &preferred_extension = std::string(), + bool allow_last_param = true, + bool allow_stdout = true); + + void add_units_options(); + void add_animation_options(); + void add_merge_externals_options(); + +protected: + void apply_units_scale(EggData *data); + void apply_parameters(SomethingToEggConverter &converter); + + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + virtual void post_process_egg_file(); + + static bool dispatch_animation_convert(const std::string &opt, const std::string &arg, void *var); + + + Filename _input_filename; + + DistanceUnit _input_units; + DistanceUnit _output_units; + + AnimationConvert _animation_convert; + std::string _character_name; + double _start_frame; + double _end_frame; + double _frame_inc; + double _neutral_frame; + double _input_frame_rate; + double _output_frame_rate; + bool _got_start_frame; + bool _got_end_frame; + bool _got_frame_inc; + bool _got_neutral_frame; + bool _got_input_frame_rate; + bool _got_output_frame_rate; + + bool _merge_externals; + bool _noexist; + bool _allow_errors; +}; + +#endif diff --git a/pandatool/src/eggcharbase/CMakeLists.txt b/pandatool/src/eggcharbase/CMakeLists.txt new file mode 100644 index 00000000..384bebe0 --- /dev/null +++ b/pandatool/src/eggcharbase/CMakeLists.txt @@ -0,0 +1,46 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3EGGCHARBASE_HEADERS + config_eggcharbase.h + eggBackPointer.h + eggCharacterCollection.h eggCharacterCollection.I + eggCharacterData.h eggCharacterData.I + eggCharacterDb.h eggCharacterDb.I + eggCharacterFilter.h + eggComponentData.h eggComponentData.I + eggJointData.h eggJointData.I + eggJointNodePointer.h + eggJointPointer.h eggJointPointer.I + eggMatrixTablePointer.h + eggScalarTablePointer.h + eggSliderData.h eggSliderData.I + eggSliderPointer.h + eggVertexPointer.h +) + +set(P3EGGCHARBASE_SOURCES + config_eggcharbase.cxx + eggBackPointer.cxx + eggCharacterCollection.cxx + eggCharacterData.cxx + eggCharacterDb.cxx + eggCharacterFilter.cxx + eggComponentData.cxx + eggJointData.cxx + eggJointNodePointer.cxx + eggJointPointer.cxx + eggMatrixTablePointer.cxx + eggScalarTablePointer.cxx + eggSliderData.cxx + eggSliderPointer.cxx + eggVertexPointer.cxx +) + +composite_sources(p3eggcharbase P3EGGCHARBASE_SOURCES) +add_library(p3eggcharbase STATIC ${P3EGGCHARBASE_HEADERS} ${P3EGGCHARBASE_SOURCES}) +target_link_libraries(p3eggcharbase p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/eggcharbase/config_eggcharbase.cxx b/pandatool/src/eggcharbase/config_eggcharbase.cxx new file mode 100644 index 00000000..734939a4 --- /dev/null +++ b/pandatool/src/eggcharbase/config_eggcharbase.cxx @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_eggcharbase.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "config_eggcharbase.h" +#include "eggBackPointer.h" +#include "eggComponentData.h" +#include "eggJointData.h" +#include "eggJointNodePointer.h" +#include "eggJointPointer.h" +#include "eggMatrixTablePointer.h" +#include "eggScalarTablePointer.h" +#include "eggSliderData.h" +#include "eggSliderPointer.h" +#include "eggVertexPointer.h" + +#include "dconfig.h" + + +Configure(config_eggcharbase); + +// NotifyCategoryDef(eggcharbase, ""); + +ConfigureFn(config_eggcharbase) { + init_libeggcharbase(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libeggcharbase() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + EggBackPointer::init_type(); + EggComponentData::init_type(); + EggJointData::init_type(); + EggJointNodePointer::init_type(); + EggJointPointer::init_type(); + EggMatrixTablePointer::init_type(); + EggScalarTablePointer::init_type(); + EggSliderData::init_type(); + EggSliderPointer::init_type(); + EggVertexPointer::init_type(); +} diff --git a/pandatool/src/eggcharbase/config_eggcharbase.h b/pandatool/src/eggcharbase/config_eggcharbase.h new file mode 100644 index 00000000..d6d1404e --- /dev/null +++ b/pandatool/src/eggcharbase/config_eggcharbase.h @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_eggcharbase.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef CONFIG_EGGCHARBASE_H +#define CONFIG_EGGCHARBASE_H + +#include "pandabase.h" + +// Commented out to resolve link problem #include "notifyCategoryProxy.h" +// NotifyCategoryDecl(eggcharbase, EXPCL_MISC, EXPTP_MISC); + +extern void init_libeggcharbase(); + +#endif diff --git a/pandatool/src/eggcharbase/eggBackPointer.cxx b/pandatool/src/eggcharbase/eggBackPointer.cxx new file mode 100644 index 00000000..cc6110d0 --- /dev/null +++ b/pandatool/src/eggcharbase/eggBackPointer.cxx @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggBackPointer.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggBackPointer.h" + +#include "pnotify.h" + + +TypeHandle EggBackPointer::_type_handle; + +/** + * + */ +EggBackPointer:: +EggBackPointer() { +} + +/** + * Returns the stated frame rate of this particular joint, or 0.0 if it + * doesn't state. + */ +double EggBackPointer:: +get_frame_rate() const { + return 0.0; +} + +/** + * Extends the table to the indicated number of frames. + */ +void EggBackPointer:: +extend_to(int num_frames) { + // Whoops, can't extend this kind of table! + nassert_raise("can't extend this kind of table"); +} + +/** + * Returns true if there are any vertices referenced by the node this points + * to, false otherwise. For certain kinds of back pointers (e.g. table + * animation entries), this is always false. + */ +bool EggBackPointer:: +has_vertices() const { + return false; +} + +/** + * Applies the indicated name change to the egg file. + */ +void EggBackPointer:: +set_name(const std::string &name) { +} diff --git a/pandatool/src/eggcharbase/eggBackPointer.h b/pandatool/src/eggcharbase/eggBackPointer.h new file mode 100644 index 00000000..2595c09d --- /dev/null +++ b/pandatool/src/eggcharbase/eggBackPointer.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggBackPointer.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGBACKPOINTER_H +#define EGGBACKPOINTER_H + +#include "pandatoolbase.h" + +#include "typedObject.h" + +/** + * This stores a pointer from an EggJointData or EggSliderData object back to + * the referencing data in an egg file. One of these objects corresponds to + * each model appearing in an egg file, and may reference either a single + * node, or a table, or a slew of vertices and primitives, depending on the + * type of data stored. + * + * This is just an abstract base class. The actual details are stored in the + * various subclasses. + */ +class EggBackPointer : public TypedObject { +public: + EggBackPointer(); + + virtual double get_frame_rate() const; + virtual int get_num_frames() const=0; + virtual void extend_to(int num_frames); + virtual bool has_vertices() const; + + virtual void set_name(const std::string &name); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedObject::init_type(); + register_type(_type_handle, "EggBackPointer", + TypedObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggCharacterCollection.I b/pandatool/src/eggcharbase/eggCharacterCollection.I new file mode 100644 index 00000000..fc1f2696 --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterCollection.I @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterCollection.I + * @author drose + * @date 2001-02-26 + */ + +/** + * Returns the number of egg files that have successfully been added to the + * Character table. + */ +INLINE int EggCharacterCollection:: +get_num_eggs() const { + return _eggs.size(); +} + +/** + * Returns the ith egg file. + */ +INLINE EggData *EggCharacterCollection:: +get_egg(int i) const { + nassertr(i >= 0 && i < (int)_eggs.size(), nullptr); + return _eggs[i]._egg; +} + +/** + * Returns the first model index associated with the indicated egg file. An + * egg file may contain multiple models, which will be consecutive integers + * beginning at get_first_model_index() and continuing for get_num_models(). + * + * Each "model" corresponds to a single character model, or one LOD of a + * multiple-LOD model, or a single animation bundle. + */ +INLINE int EggCharacterCollection:: +get_first_model_index(int egg_index) const { + nassertr(egg_index >= 0 && egg_index < (int)_eggs.size(), 0); + return _eggs[egg_index]._first_model_index; +} + +/** + * Returns the number of different models found in the indicated egg file. An + * egg file may contain multiple models, which will be consecutive integers + * beginning at get_first_model_index() and continuing for get_num_models(). + * + * Each "model" corresponds to a single character model, or one LOD of a + * multiple-LOD model, or a single animation bundle. + */ +INLINE int EggCharacterCollection:: +get_num_models(int egg_index) const { + nassertr(egg_index >= 0 && egg_index < (int)_eggs.size(), 0); + return _eggs[egg_index]._models.size(); +} + +/** + * Returns the number of separate Characters that have been discovered in the + * various egg files added to the collection. + */ +INLINE int EggCharacterCollection:: +get_num_characters() const { + return _characters.size(); +} + +/** + * Returns the ith character in the collection. + */ +INLINE EggCharacterData *EggCharacterCollection:: +get_character(int i) const { + nassertr(i >= 0 && i < (int)_characters.size(), nullptr); + return _characters[i]; +} + +/** + * Returns the character associated with the indicated model index. + */ +INLINE EggCharacterData *EggCharacterCollection:: +get_character_by_model_index(int model_index) const { + nassertr(model_index >= 0 && model_index < (int)_characters_by_model_index.size(), + nullptr); + return _characters_by_model_index[model_index]; +} + +/** + * + */ +INLINE EggCharacterCollection::ModelDescription:: +ModelDescription() { + _root_node = nullptr; +} diff --git a/pandatool/src/eggcharbase/eggCharacterCollection.cxx b/pandatool/src/eggcharbase/eggCharacterCollection.cxx new file mode 100644 index 00000000..f90c7d9a --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterCollection.cxx @@ -0,0 +1,680 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterCollection.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggCharacterCollection.h" +#include "eggCharacterData.h" +#include "eggJointData.h" +#include "eggSliderData.h" + +#include "dcast.h" +#include "eggGroup.h" +#include "eggTable.h" +#include "eggPrimitive.h" +#include "eggVertex.h" +#include "eggVertexUV.h" +#include "eggMorphList.h" +#include "eggSAnimData.h" +#include "indirectCompareNames.h" +#include "indent.h" + +#include + +using std::string; + + +/** + * + */ +EggCharacterCollection:: +EggCharacterCollection() { + _next_model_index = 0; +} + +/** + * + */ +EggCharacterCollection:: +~EggCharacterCollection() { + Characters::iterator ci; + + for (ci = _characters.begin(); ci != _characters.end(); ++ci) { + delete (*ci); + } +} + +/** + * Adds a new egg file to the list of models and animation files for this + * particular character. + * + * Returns the new egg_index if the file is successfully added, or -1 if there + * is some problem (for instance, it does not contain a character model or + * animation table). + * + * If the joint hierarchy does not match the existing joint hierarchy, a best + * match is attempted. + */ +int EggCharacterCollection:: +add_egg(EggData *egg) { + _top_egg_nodes.clear(); + + if (!scan_hierarchy(egg)) { + return -1; + } + + int egg_index = _eggs.size(); + _eggs.push_back(EggInfo()); + EggInfo &egg_info = _eggs.back(); + egg_info._egg = egg; + egg_info._first_model_index = 0; + + // Now, for each model, add an entry in the egg_info and match the joint + // hierarchy to the known joints. + TopEggNodesByName::iterator tni; + for (tni = _top_egg_nodes.begin(); tni != _top_egg_nodes.end(); ++tni) { + string character_name = (*tni).first; + TopEggNodes &top_nodes = (*tni).second; + EggCharacterData *char_data = make_character(character_name); + EggJointData *root_joint = char_data->get_root_joint(); + + TopEggNodes::iterator ti; + for (ti = top_nodes.begin(); ti != top_nodes.end(); ++ti) { + EggNode *model_root = (*ti).first; + ModelDescription &desc = (*ti).second; + + int model_index = _next_model_index++; + if (egg_info._models.empty()) { + egg_info._first_model_index = model_index; + } + egg_info._models.push_back(model_root); + + char_data->add_model(model_index, model_root, egg); + nassertr(model_index == (int)_characters_by_model_index.size(), -1); + _characters_by_model_index.push_back(char_data); + root_joint->add_back_pointer(model_index, desc._root_node); + + match_egg_nodes(char_data, root_joint, desc._top_nodes, + egg_index, model_index); + + scan_for_morphs(model_root, model_index, char_data); + scan_for_sliders(model_root, model_index, char_data); + } + } + + return egg_index; +} + +/** + * Returns the Character with the indicated name, if it exists in the + * collection, or NULL if it does not. + */ +EggCharacterData *EggCharacterCollection:: +get_character_by_name(const string &character_name) const { + Characters::const_iterator ci; + for (ci = _characters.begin(); ci != _characters.end(); ++ci) { + EggCharacterData *char_data = (*ci); + if (char_data->get_name() == character_name) { + return char_data; + } + } + + return nullptr; +} + + +/** + * Allocates and returns a new EggCharacterData structure. This is primarily + * intended as a hook so derived classes can customize the type of + * EggCharacterData nodes used to represent the characters in this collection. + */ +EggCharacterData *EggCharacterCollection:: +make_character_data() { + return new EggCharacterData(this); +} + +/** + * Allocates and returns a new EggJointData structure for the given character. + * This is primarily intended as a hook so derived classes can customize the + * type of EggJointData nodes used to represent the joint hierarchy. + */ +EggJointData *EggCharacterCollection:: +make_joint_data(EggCharacterData *char_data) { + return new EggJointData(this, char_data); +} + +/** + * Allocates and returns a new EggSliderData structure for the given + * character. This is primarily intended as a hook so derived classes can + * customize the type of EggSliderData nodes used to represent the slider + * list. + */ +EggSliderData *EggCharacterCollection:: +make_slider_data(EggCharacterData *char_data) { + return new EggSliderData(this, char_data); +} + +/** + * Allocates and returns a new EggCharacterData object representing the named + * character, if there is not already a character by that name. + */ +EggCharacterData *EggCharacterCollection:: +make_character(const string &character_name) { + // Does the named character exist yet? + + Characters::iterator ci; + for (ci = _characters.begin(); ci != _characters.end(); ++ci) { + EggCharacterData *char_data = (*ci); + if (char_data->get_name() == character_name) { + return char_data; + } + } + + // Define a new character. + EggCharacterData *char_data = make_character_data(); + char_data->set_name(character_name); + _characters.push_back(char_data); + return char_data; +} + +/** + * Walks the given egg data's hierarchy, looking for either the start of an + * animation channel or the start of a character model. Returns true if + * either (or both) is found, false if the model appears to have nothing to do + * with characters. + * + * Fills up the _top_egg_nodes according to the nodes found. + */ +bool EggCharacterCollection:: +scan_hierarchy(EggNode *egg_node) { + if (egg_node->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, egg_node); + if (group->get_dart_type() != EggGroup::DT_none) { + // A group with a flag begins a character model. + scan_for_top_joints(group, group, group->get_name()); + return true; + } + + } else if (egg_node->is_of_type(EggTable::get_class_type())) { + EggTable *table = DCAST(EggTable, egg_node); + if (table->get_table_type() == EggTable::TT_bundle) { + // A begins an animation table. + scan_for_top_tables(table, table, table->get_name()); + return true; + } + } + + bool character_found = false; + if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, egg_node); + EggGroupNode::iterator gi; + for (gi = group->begin(); gi != group->end(); ++gi) { + if (scan_hierarchy(*gi)) { + character_found = true; + } + } + } + + return character_found; +} + +/** + * Once a character model has been found, continue scanning the egg hierarchy + * to look for the topmost nodes encountered. + */ +void EggCharacterCollection:: +scan_for_top_joints(EggNode *egg_node, EggNode *model_root, + const string &character_name) { + if (egg_node->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, egg_node); + + if (group->has_lod()) { + // This group has an LOD specification; that indicates multiple skeleton + // hierarchies for this character, one for each LOD. We call each of + // these a separate model. + model_root = group; + } + if (group->get_group_type() == EggGroup::GT_joint) { + // A node begins a model hierarchy. + ModelDescription &desc = _top_egg_nodes[character_name][model_root]; + desc._root_node = model_root; + desc._top_nodes.push_back(group); + return; + } + } + + if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, egg_node); + EggGroupNode::iterator gi; + for (gi = group->begin(); gi != group->end(); ++gi) { + scan_for_top_joints(*gi, model_root, character_name); + } + } +} + +/** + * Once an animation has been found, continue scanning the egg hierarchy to + * look for the topmost nodes encountered. + */ +void EggCharacterCollection:: +scan_for_top_tables(EggTable *bundle, EggNode *model_root, + const string &character_name) { + // We really only need to check the immediate children of the bundle for a + // table node called "". + EggGroupNode::iterator gi; + for (gi = bundle->begin(); gi != bundle->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggTable::get_class_type())) { + EggTable *table = DCAST(EggTable, child); + if (table->get_name() == "") { + // Here it is! Now the immediate children of this node are the top + // tables. + ModelDescription &desc = _top_egg_nodes[character_name][model_root]; + desc._root_node = table; + + EggGroupNode::iterator cgi; + for (cgi = table->begin(); cgi != table->end(); ++cgi) { + EggNode *grandchild = (*cgi); + if (grandchild->is_of_type(EggTable::get_class_type())) { + desc._top_nodes.push_back(grandchild); + } + } + } + } + } +} + +/** + * Go back through a model's hierarchy and look for morph targets on the + * vertices and primitives. + */ +void EggCharacterCollection:: +scan_for_morphs(EggNode *egg_node, int model_index, + EggCharacterData *char_data) { + if (egg_node->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *prim = DCAST(EggPrimitive, egg_node); + // Check for morphs on the primitive. + add_morph_back_pointers(prim, prim, model_index, char_data); + + // Also check for morphs on each of the prim's vertices. + EggPrimitive::const_iterator vi; + for (vi = prim->begin(); vi != prim->end(); ++vi) { + EggVertex *vertex = (*vi); + + add_morph_back_pointers(vertex, vertex, model_index, char_data); + add_morph_back_pointers_vertex(vertex, vertex, model_index, char_data); + + EggMorphVertexList::const_iterator mvi; + for (mvi = vertex->_dxyzs.begin(); + mvi != vertex->_dxyzs.end(); + ++mvi) { + const EggMorphVertex &morph = (*mvi); + char_data->make_slider(morph.get_name())->add_back_pointer(model_index, vertex); + } + } + } + + if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, egg_node); + EggGroupNode::iterator gi; + for (gi = group->begin(); gi != group->end(); ++gi) { + scan_for_morphs(*gi, model_index, char_data); + } + } +} + +/** + * Go back to the animation tables and look for morph slider animation + * channels. + */ +void EggCharacterCollection:: +scan_for_sliders(EggNode *egg_node, int model_index, + EggCharacterData *char_data) { + if (egg_node->is_of_type(EggTable::get_class_type())) { + EggTable *bundle = DCAST(EggTable, egg_node); + + // We really only need to check the immediate children of the bundle for a + // table node called "morph". This is a sibling of "", which we + // found a minute ago, but we weren't ready to scan for the morph sliders + // at the time, so we have to look again now. + + EggGroupNode::iterator gi; + for (gi = bundle->begin(); gi != bundle->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggTable::get_class_type())) { + EggTable *table = DCAST(EggTable, child); + if (table->get_name() == "morph") { + // Here it is! Now the immediate children of this node are all the + // slider channels. + + EggGroupNode::iterator cgi; + for (cgi = table->begin(); cgi != table->end(); ++cgi) { + EggNode *grandchild = (*cgi); + if (grandchild->is_of_type(EggSAnimData::get_class_type())) { + char_data->make_slider(grandchild->get_name())->add_back_pointer(model_index, grandchild); + } + } + } + } + } + } +} + +/** + * Adds the back pointers for the kinds of morphs we might find in an + * EggAttributes object. + */ +void EggCharacterCollection:: +add_morph_back_pointers(EggAttributes *attrib, EggObject *egg_object, + int model_index, EggCharacterData *char_data) { + EggMorphNormalList::const_iterator mni; + for (mni = attrib->_dnormals.begin(); + mni != attrib->_dnormals.end(); + ++mni) { + const EggMorphNormal &morph = (*mni); + char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object); + } + + EggMorphColorList::const_iterator mci; + for (mci = attrib->_drgbas.begin(); + mci != attrib->_drgbas.end(); + ++mci) { + const EggMorphColor &morph = (*mci); + char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object); + } +} + +/** + * Adds the back pointers for the kinds of morphs we might find in an + * EggVertex object. + */ +void EggCharacterCollection:: +add_morph_back_pointers_vertex(EggVertex *vertex, EggObject *egg_object, + int model_index, EggCharacterData *char_data) { + EggVertex::const_uv_iterator ui; + for (ui = vertex->uv_begin(); ui != vertex->uv_end(); ++ui) { + EggVertexUV *vert_uv = (*ui); + EggMorphTexCoordList::const_iterator mti; + for (mti = vert_uv->_duvs.begin(); + mti != vert_uv->_duvs.end(); + ++mti) { + const EggMorphTexCoord &morph = (*mti); + char_data->make_slider(morph.get_name())->add_back_pointer(model_index, egg_object); + } + } +} + +/** + * Attempts to match up the indicated list of egg_nodes with the children of + * the given joint_data, by name if possible. + * + * Also recurses on each matched joint to build up the entire joint hierarchy. + */ +void EggCharacterCollection:: +match_egg_nodes(EggCharacterData *char_data, EggJointData *joint_data, + EggNodeList &egg_nodes, int egg_index, int model_index) { + // Sort the list of egg_nodes in order by name. This will make the matching + // up by names easier and more reliable. + sort(egg_nodes.begin(), egg_nodes.end(), IndirectCompareNames()); + + if (joint_data->_children.empty()) { + // If the EggJointData has no children yet, we must be the first. + // Gleefully define all the joints. + EggNodeList::iterator ei; + for (ei = egg_nodes.begin(); ei != egg_nodes.end(); ++ei) { + EggNode *egg_node = (*ei); + EggJointData *data = make_joint_data(char_data); + joint_data->_children.push_back(data); + char_data->_joints.push_back(data); + char_data->_components.push_back(data); + data->_parent = joint_data; + data->_new_parent = joint_data; + found_egg_match(char_data, data, egg_node, egg_index, model_index); + } + + } else { + // The EggJointData already has children; therefore, we have to match our + // joints up with the already-existing ones. + + EggNodeList extra_egg_nodes; + EggJointData::Children extra_data; + + EggNodeList::iterator ei; + EggJointData::Children::iterator di; + + ei = egg_nodes.begin(); + di = joint_data->_children.begin(); + + while (ei != egg_nodes.end() && di != joint_data->_children.end()) { + EggNode *egg_node = (*ei); + EggJointData *data = (*di); + + if (egg_node->get_name() < data->get_name()) { + // Here's a joint in the egg file, unmatched in the data. + extra_egg_nodes.push_back(egg_node); + ++ei; + + } else if (data->get_name() < egg_node->get_name()) { + // Here's a joint in the data, umatched by the egg file. + extra_data.push_back(data); + ++di; + + } else { + // Hey, these two match! Hooray! + found_egg_match(char_data, data, egg_node, egg_index, model_index); + ++ei; + ++di; + } + } + + while (ei != egg_nodes.end()) { + EggNode *egg_node = (*ei); + + // Here's a joint in the egg file, unmatched in the data. + extra_egg_nodes.push_back(egg_node); + ++ei; + } + + while (di != joint_data->_children.end()) { + EggJointData *data = (*di); + + // Here's a joint in the data, umatched by the egg file. + extra_data.push_back(data); + ++di; + } + + if (!extra_egg_nodes.empty()) { + // If we have some extra egg_nodes, we have to find a place to match + // them. (If we only had extra data, we don't care.) + + // First, check to see if any of the names match any past-used name. + EggNodeList more_egg_nodes; + + for (ei = extra_egg_nodes.begin(); ei != extra_egg_nodes.end(); ++ei) { + EggNode *egg_node = (*ei); + bool matched = false; + for (di = extra_data.begin(); di != extra_data.end(); ++di) { + EggJointData *data = (*di); + if (data->matches_name(egg_node->get_name())) { + found_egg_match(char_data, data, egg_node, egg_index, model_index); + extra_data.erase(di); + matched = true; + break; + } + } + + if (!matched) { + // This joint name was never seen before. + more_egg_nodes.push_back(egg_node); + } + } + extra_egg_nodes.swap(more_egg_nodes); + } + + if (!extra_egg_nodes.empty()) { + // Ok, we've still got to find a home for these remaining egg_nodes. + if (extra_egg_nodes.size() == extra_data.size()) { + // Match 'em up one-for-one. + size_t i; + for (i = 0; i < extra_egg_nodes.size(); i++) { + EggNode *egg_node = extra_egg_nodes[i]; + EggJointData *data = extra_data[i]; + found_egg_match(char_data, data, egg_node, egg_index, model_index); + } + + } else { + // Just tack 'em on the end. + EggNodeList::iterator ei; + for (ei = extra_egg_nodes.begin(); ei != extra_egg_nodes.end(); ++ei) { + EggNode *egg_node = (*ei); + EggJointData *data = make_joint_data(char_data); + joint_data->_children.push_back(data); + char_data->_joints.push_back(data); + char_data->_components.push_back(data); + data->_parent = joint_data; + data->_new_parent = joint_data; + found_egg_match(char_data, data, egg_node, egg_index, model_index); + } + } + } + } + + // Now sort the generated joint data hierarchy by name, just to be sure. + sort(joint_data->_children.begin(), joint_data->_children.end(), + IndirectCompareNames()); +} + +/** + * Marks a one-to-one association between the indicated EggJointData and the + * indicated EggNode, and then recurses below. + */ +void EggCharacterCollection:: +found_egg_match(EggCharacterData *char_data, EggJointData *joint_data, + EggNode *egg_node, int egg_index, int model_index) { + if (egg_node->has_name()) { + joint_data->add_name(egg_node->get_name(), char_data->_component_names); + } + egg_node->set_name(joint_data->get_name()); + joint_data->add_back_pointer(model_index, egg_node); + + if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group_node = DCAST(EggGroupNode, egg_node); + + // Now consider all the children of egg_node that are themselves joints or + // tables. + EggNodeList egg_nodes; + + // Two approaches: either we are scanning a model with joints, or an + // animation bundle with tables. + + if (egg_node->is_of_type(EggGroup::get_class_type())) { + // A model with joints. + EggGroupNode::iterator gi; + for (gi = group_node->begin(); gi != group_node->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, child); + if (group->get_group_type() == EggGroup::GT_joint) { + egg_nodes.push_back(group); + } + } + } + + } else { + // An animation bundle with tables. + EggGroupNode::iterator gi; + for (gi = group_node->begin(); gi != group_node->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggTable::get_class_type())) { + EggTable *table = DCAST(EggTable, child); + if (!(table->get_name() == "xform")) { + egg_nodes.push_back(table); + } + } + } + } + + if (!egg_nodes.empty()) { + match_egg_nodes(char_data, joint_data, egg_nodes, + egg_index, model_index); + } + } +} + +/** + * Renames the ith character to the indicated name. This name must not + * already be used by another character in the collection. + */ +void EggCharacterCollection:: +rename_char(int i, const string &name) { + nassertv(i >= 0 && i < (int)_characters.size()); + + EggCharacterData *char_data = _characters[i]; + if (char_data->get_name() != name) { + nassertv(get_character_by_name(name) == nullptr); + char_data->rename_char(name); + } +} + +/** + * + */ +void EggCharacterCollection:: +write(std::ostream &out, int indent_level) const { + Characters::const_iterator ci; + + for (ci = _characters.begin(); ci != _characters.end(); ++ci) { + EggCharacterData *char_data = (*ci); + char_data->write(out, indent_level); + } +} + +/** + * Can be called after the collection has been completely filled up with egg + * files to output any messages from warning conditions that have been + * detected, such as inconsistent animation tables. + * + * In addition to reporting this errors, calling this function will also + * ensure that they are all repaired. Pass force_initial_rest_frame as true + * to also force rest frames from different models to be the same if they are + * initially different. + */ +void EggCharacterCollection:: +check_errors(std::ostream &out, bool force_initial_rest_frame) { + Characters::const_iterator ci; + for (ci = _characters.begin(); ci != _characters.end(); ++ci) { + EggCharacterData *char_data = (*ci); + int num_joints = char_data->get_num_joints(); + for (int j = 0; j < num_joints; j++) { + EggJointData *joint_data = char_data->get_joint(j); + if (joint_data->rest_frames_differ()) { + if (force_initial_rest_frame) { + joint_data->force_initial_rest_frame(); + out << "Forced rest frames the same for " << joint_data->get_name() + << ".\n"; + } else { + out << "Warning: rest frames for " << joint_data->get_name() + << " differ.\n"; + } + } + } + + int num_models = char_data->get_num_models(); + for (int mi = 0; mi < num_models; mi++) { + int model_index = char_data->get_model_index(mi); + if (!char_data->check_num_frames(model_index)) { + out << "Warning: animation from " + << char_data->get_egg_data(model_index)->get_egg_filename().get_basename() + << " had an inconsistent number of frames.\n"; + } + } + } +} diff --git a/pandatool/src/eggcharbase/eggCharacterCollection.h b/pandatool/src/eggcharbase/eggCharacterCollection.h new file mode 100644 index 00000000..8a0bbd4f --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterCollection.h @@ -0,0 +1,117 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterCollection.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGCHARACTERCOLLECTION_H +#define EGGCHARACTERCOLLECTION_H + +#include "pandatoolbase.h" + +#include "eggCharacterData.h" + +#include "eggData.h" +#include "eggNode.h" +#include "pointerTo.h" + +class EggTable; +class EggAttributes; + +/** + * Represents a set of characters, as read and collected from possibly several + * model and/or animation egg files. + */ +class EggCharacterCollection { +public: + EggCharacterCollection(); + virtual ~EggCharacterCollection(); + + int add_egg(EggData *egg); + + INLINE int get_num_eggs() const; + INLINE EggData *get_egg(int i) const; + INLINE int get_first_model_index(int egg_index) const; + INLINE int get_num_models(int egg_index) const; + + INLINE int get_num_characters() const; + INLINE EggCharacterData *get_character(int i) const; + EggCharacterData *get_character_by_name(const std::string &character_name) const; + + INLINE EggCharacterData *get_character_by_model_index(int model_index) const; + + void rename_char(int i, const std::string &name); + + virtual void write(std::ostream &out, int indent_level = 0) const; + void check_errors(std::ostream &out, bool force_initial_rest_frame); + + virtual EggCharacterData *make_character_data(); + virtual EggJointData *make_joint_data(EggCharacterData *char_data); + virtual EggSliderData *make_slider_data(EggCharacterData *char_data); + +public: + EggCharacterData *make_character(const std::string &character_name); + + class EggInfo { + public: + PT(EggData) _egg; + typedef pvector< PT(EggNode) > Models; + Models _models; + int _first_model_index; + }; + + typedef pvector Eggs; + Eggs _eggs; + + typedef pvector Characters; + Characters _characters; + Characters _characters_by_model_index; + +private: + bool scan_hierarchy(EggNode *egg_node); + void scan_for_top_joints(EggNode *egg_node, EggNode *model_root, + const std::string &character_name); + void scan_for_top_tables(EggTable *bundle, EggNode *model_root, + const std::string &character_name); + void scan_for_morphs(EggNode *egg_node, int model_index, + EggCharacterData *char_data); + void scan_for_sliders(EggNode *egg_node, int model_index, + EggCharacterData *char_data); + + void add_morph_back_pointers(EggAttributes *attrib, EggObject *egg_object, + int model_index, EggCharacterData *char_data); + void add_morph_back_pointers_vertex(EggVertex *vertex, EggObject *egg_object, + int model_index, EggCharacterData *char_data); + + // The _top_egg_nodes member is only used temporarily, when adding each pre- + // existing egg file to the structure for the first time. + typedef pvector EggNodeList; + class ModelDescription { + public: + INLINE ModelDescription(); + EggNodeList _top_nodes; + EggObject *_root_node; + }; + + typedef pmap TopEggNodes; + typedef pmap TopEggNodesByName; + TopEggNodesByName _top_egg_nodes; + + int _next_model_index; + + void match_egg_nodes(EggCharacterData *char_Data, EggJointData *joint_data, + EggNodeList &egg_nodes, int egg_index, int model_index); + void found_egg_match(EggCharacterData *char_data, EggJointData *joint_data, + EggNode *egg_node, int egg_index, int model_index); +}; + +#include "eggCharacterCollection.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggCharacterData.I b/pandatool/src/eggcharbase/eggCharacterData.I new file mode 100644 index 00000000..fc3d3f1b --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterData.I @@ -0,0 +1,150 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterData.I + * @author drose + * @date 2001-02-23 + */ + +/** + * Returns the total number of models associated with this character. + * + * A "model" here is either a character model (or one LOD of a character + * model), or a character animation file: in either case, a hierarchy of + * joints. + */ +INLINE int EggCharacterData:: +get_num_models() const { + return _models.size(); +} + +/** + * Returns the model_index of the nth model associated with this character. + * This model_index may be used to ask questions about the particular model + * from the EggCharacterCollection object, or from the individual EggJointData + * and EggSliderData objects. + * + * A "model" here is either a character model (or one LOD of a character + * model), or a character animation file: in either case, a hierarchy of + * joints. + */ +INLINE int EggCharacterData:: +get_model_index(int n) const { + nassertr(n >= 0 && n < (int)_models.size(), 0); + return _models[n]._model_index; +} + +/** + * Returns the model_root of the nth model associated with this character. + * + * This is the node at which the character, animation bundle, or LOD + * officially began within its particular egg file. + */ +INLINE EggNode *EggCharacterData:: +get_model_root(int n) const { + nassertr(n >= 0 && n < (int)_models.size(), nullptr); + return _models[n]._model_root; +} + +/** + * Returns the EggData representing the egg file that defined this particular + * model. Note that one egg file might contain multiple models. + */ +INLINE EggData *EggCharacterData:: +get_egg_data(int n) const { + nassertr(n >= 0 && n < (int)_models.size(), nullptr); + return _models[n]._egg_data; +} + +/** + * Returns the root joint of the character hierarchy. This root joint does + * not represent an actual joint in the hierarchy, but instead is a fictitious + * joint that is the parent of all the top joints in the hierarchy (since the + * hierarchy may actually contain zero or more top joints). + */ +INLINE EggJointData *EggCharacterData:: +get_root_joint() const { + return _root_joint; +} + +/** + * Returns the first joint found with the indicated name, or NULL if no joint + * has that name. + */ +INLINE EggJointData *EggCharacterData:: +find_joint(const std::string &name) const { + return _root_joint->find_joint(name); +} + +/** + * Creates a new joint as a child of the indicated joint and returns it. The + * new joint will be initialized to the identity transform, so that in + * inherits the net transform of the indicated parent joint. + */ +INLINE EggJointData *EggCharacterData:: +make_new_joint(const std::string &name, EggJointData *parent) { + EggJointData *joint = parent->make_new_joint(name); + _joints.push_back(joint); + _components.push_back(joint); + return joint; +} + +/** + * Returns the total number of joints in the character joint hierarchy. + */ +INLINE int EggCharacterData:: +get_num_joints() const { + return _joints.size(); +} + +/** + * Returns the nth joint in the character joint hierarchy. This returns all + * of the joints in the hierarchy in an arbitrary ordering. + */ +INLINE EggJointData *EggCharacterData:: +get_joint(int n) const { + nassertr(n >= 0 && n < (int)_joints.size(), nullptr); + return _joints[n]; +} + +/** + * Returns the number of sliders in the character slider list. + */ +INLINE int EggCharacterData:: +get_num_sliders() const { + return _sliders.size(); +} + +/** + * Returns the nth slider in the character slider list. + */ +INLINE EggSliderData *EggCharacterData:: +get_slider(int n) const { + nassertr(n >= 0 && n < (int)_sliders.size(), nullptr); + return _sliders[n]; +} + +/** + * Returns the total number of joints and sliders in the character. + */ +INLINE int EggCharacterData:: +get_num_components() const { + return _components.size(); +} + +/** + * Returns the nth joint or slider in the character. This can be used to walk + * linearly through all joints and sliders in the character when you don't + * care about making a distinction between the two; it returns the same + * objects that can also be discovered via get_slider() and get_root_joint(). + */ +INLINE EggComponentData *EggCharacterData:: +get_component(int n) const { + nassertr(n >= 0 && n < (int)_components.size(), nullptr); + return _components[n]; +} diff --git a/pandatool/src/eggcharbase/eggCharacterData.cxx b/pandatool/src/eggcharbase/eggCharacterData.cxx new file mode 100644 index 00000000..d3c7a545 --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterData.cxx @@ -0,0 +1,410 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterData.cxx + * @author drose + * @date 2001-02-23 + */ + +#include "eggCharacterData.h" +#include "eggCharacterCollection.h" +#include "eggCharacterDb.h" +#include "eggJointData.h" +#include "eggSliderData.h" +#include "indent.h" + +#include + +// An STL function object to sort the joint list in order from highest to +// lowest in the new hierarchy. Used in do_reparent(). +class OrderJointsByNewDepth { +public: + bool operator()(const EggJointData *a, const EggJointData *b) const { + return a->_new_parent_depth < b->_new_parent_depth; + } +}; + + +/** + * + */ +EggCharacterData:: +EggCharacterData(EggCharacterCollection *collection) : + _component_names("_", "joint_") +{ + _collection = collection; + _root_joint = _collection->make_joint_data(this); + // The fictitious root joint is not added to the _components list. +} + +/** + * + */ +EggCharacterData:: +~EggCharacterData() { + delete _root_joint; + + Sliders::iterator si; + for (si = _sliders.begin(); si != _sliders.end(); ++si) { + EggSliderData *slider = (*si); + delete slider; + } +} + +/** + * Renames all of the models in the character data to the indicated name. + * This is the name that is used to identify unique skeleton hierarchies; if + * you set two different models to the same name, they will be loaded together + * as if they are expected to have the same skeleton hierarchy. + */ +void EggCharacterData:: +rename_char(const std::string &name) { + Models::iterator mi; + for (mi = _models.begin(); mi != _models.end(); ++mi) { + (*mi)._model_root->set_name(name); + } + + set_name(name); +} + +/** + * Indicates that the given model_index (with the indicated model_root) is + * associated with this character. This is normally called by the + * EggCharacterCollection class as new models are discovered. + * + * A "model" here is either a character model (or one LOD of a character + * model), or a character animation file: in either case, a hierarchy of + * joints. + */ +void EggCharacterData:: +add_model(int model_index, EggNode *model_root, EggData *egg_data) { + Model m; + m._model_index = model_index; + m._model_root = model_root; + m._egg_data = egg_data; + _models.push_back(m); +} + +/** + * Returns the number of frames of animation of the indicated model. This is + * more reliable than asking a particular joint or slider of the animation for + * its number of frames, since a particular joint may have only 1 frame (if it + * is unanimated), even though the overall animation has many frames. + */ +int EggCharacterData:: +get_num_frames(int model_index) const { + int max_num_frames = 0; + Components::const_iterator ci; + for (ci = _components.begin(); ci != _components.end(); ++ci) { + EggComponentData *component = (*ci); + int num_frames = component->get_num_frames(model_index); + if (num_frames > 1) { + // We have a winner. Assume all other components will be similar. + return num_frames; + } + max_num_frames = std::max(max_num_frames, num_frames); + } + + // Every component had either 1 frame or 0 frames. Return the maximum of + // these. + return max_num_frames; +} + +/** + * Returns the stated frame rate of the specified model. Similar to + * get_num_frames(). + */ +double EggCharacterData:: +get_frame_rate(int model_index) const { + Components::const_iterator ci; + for (ci = _components.begin(); ci != _components.end(); ++ci) { + EggComponentData *component = (*ci); + double frame_rate = component->get_frame_rate(model_index); + if (frame_rate != 0.0) { + // We have a winner. Assume all other components will be similar. + return frame_rate; + } + } + + return 0.0; +} + +/** + * Walks through each component and ensures that all have the same number of + * frames of animation (except for those that contain 0 or 1 frames, of + * course). Returns true if all are valid, false if there is a discreprency + * (in which case the shorter component are extended). + */ +bool EggCharacterData:: +check_num_frames(int model_index) { + int max_num_frames = 0; + bool any_violations = false; + Components::const_iterator ci; + for (ci = _components.begin(); ci != _components.end(); ++ci) { + EggComponentData *component = (*ci); + int num_frames = component->get_num_frames(model_index); + if (num_frames > 1 && max_num_frames > 1 && + max_num_frames != num_frames) { + // If we have two different opinions about the number of frames (other + // than 0 or 1), we have a discrepency. This is an error condition. + any_violations = true; + } + max_num_frames = std::max(max_num_frames, num_frames); + } + + if (any_violations) { + // Now go back through and force all components to the appropriate length. + for (ci = _components.begin(); ci != _components.end(); ++ci) { + EggComponentData *component = (*ci); + int num_frames = component->get_num_frames(model_index); + if (num_frames > 1 && max_num_frames != num_frames) { + component->extend_to(model_index, max_num_frames); + } + } + } + + return !any_violations; +} + +/** + * Begins the process of restructuring the joint hierarchy according to the + * previous calls to reparent_to() on various joints. This will reparent the + * joint hierachy in all models as requested, while adjusting the transforms + * as appropriate so that each joint retains the same net transform across all + * frames that it had before the operation. Returns true on success, false on + * failure. + */ +bool EggCharacterData:: +do_reparent() { + typedef pset InvalidSet; + InvalidSet invalid_set; + + // To begin, make sure the list of new_children is accurate. + Joints::const_iterator ji; + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + joint_data->do_begin_reparent(); + } + // We also need to clear the children on the root joint, but the root joint + // doesn't get any of the other operations (including finish_reparent) + // applied to it. + _root_joint->do_begin_reparent(); + + + // Now, check for cycles in the new parenting hierarchy, and also sort the + // joints in order from top to bottom in the new hierarchy. + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + pset chain; + if (joint_data->calc_new_parent_depth(chain)) { + nout << "Cycle detected in parent chain for " << joint_data->get_name() + << "!\n"; + return false; + } + } + sort(_joints.begin(), _joints.end(), OrderJointsByNewDepth()); + + // Now compute the new transforms for the joints' new positions. This is + // done recursively through the new parent hierarchy, so we can take + // advantage of caching the net value for a particular frame. + Models::const_iterator mi; + for (mi = _models.begin(); mi != _models.end(); ++mi) { + EggCharacterDb db; + int model_index = (*mi)._model_index; + int num_frames = get_num_frames(model_index); + nout << " computing " << (mi - _models.begin()) + 1 + << " of " << _models.size() + << ": " << (*mi)._egg_data->get_egg_filename() + << " (" << num_frames << " frames)\n"; + for (int f = 0; f < num_frames; f++) { + // First, walk through all the joints and flush the computed net + // transforms from before. + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + joint_data->do_begin_compute_reparent(); + } + _root_joint->do_begin_compute_reparent(); + + // Now go back through and compute the reparented transforms, caching + // net transforms as necessary. + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + if (!joint_data->do_compute_reparent(model_index, f, db)) { + // Oops, we got an invalid transform. + invalid_set.insert(joint_data); + } + } + } + + // Finally, apply the computations to the joints. + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + if (!joint_data->do_joint_rebuild(model_index, db)) { + invalid_set.insert(joint_data); + } + } + } + + // Now remove all of the old children and add in the new children. + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + joint_data->do_finish_reparent(); + } + + // Report the set of joints that failed. It really shouldn't be possible + // for any joints to fail, so if you see anything reported here, something + // went wrong at a fundamental level. Perhaps a problem with + // decompose_matrix(). + InvalidSet::const_iterator si; + for (si = invalid_set.begin(); si != invalid_set.end(); ++si) { + EggJointData *joint_data = (*si); + // Don't bother reporting joints that no longer have a parent, since we + // don't care about joints that are now outside the hierarchy. + if (joint_data->get_parent() != nullptr) { + nout << "Warning: reparenting " << joint_data->get_name() + << " to "; + if (joint_data->get_parent() == _root_joint) { + nout << "the root"; + } else { + nout << joint_data->get_parent()->get_name(); + } + nout << " results in an invalid transform.\n"; + } + } + + return invalid_set.empty(); +} + +/** + * Chooses the best possible parent joint for each of the joints in the + * hierarchy, based on the score computed by + * EggJointData::score_reparent_to(). This is a fairly expensive operation + * that involves lots of recomputing of transforms across the hierarchy. + * + * The joints are not actually reparented yet, but the new_parent of each + * joint is set. Call do_reparent() to actually perform the suggested + * reparenting operation. + */ +void EggCharacterData:: +choose_optimal_hierarchy() { + EggCharacterDb db; + + Joints::const_iterator ji, jj; + for (ji = _joints.begin(); ji != _joints.end(); ++ji) { + EggJointData *joint_data = (*ji); + + EggJointData *best_parent = joint_data->get_parent(); + int best_score = joint_data->score_reparent_to(best_parent, db); + + for (jj = _joints.begin(); jj != _joints.end(); ++jj) { + EggJointData *possible_parent = (*jj); + if (possible_parent != joint_data && possible_parent != best_parent && + !joint_data->is_new_ancestor(possible_parent)) { + + int score = joint_data->score_reparent_to(possible_parent, db); + if (score >= 0 && (best_score < 0 || score < best_score)) { + best_parent = possible_parent; + best_score = score; + } + } + } + + // Also consider reparenting the node to the root. + EggJointData *possible_parent = get_root_joint(); + if (possible_parent != best_parent) { + int score = joint_data->score_reparent_to(possible_parent, db); + if (score >= 0 && (best_score < 0 || score < best_score)) { + best_parent = possible_parent; + best_score = score; + } + } + + if (best_parent != nullptr && + best_parent != joint_data->_parent) { + nout << "best parent for " << joint_data->get_name() << " is " + << best_parent->get_name() << "\n"; + joint_data->reparent_to(best_parent); + } + } +} + +/** + * Returns the slider with the indicated name, or NULL if no slider has that + * name. + */ +EggSliderData *EggCharacterData:: +find_slider(const std::string &name) const { + SlidersByName::const_iterator si; + si = _sliders_by_name.find(name); + if (si != _sliders_by_name.end()) { + return (*si).second; + } + + return nullptr; +} + +/** + * Returns the slider matching the indicated name. If no such slider exists + * already, creates a new one. + */ +EggSliderData *EggCharacterData:: +make_slider(const std::string &name) { + SlidersByName::const_iterator si; + si = _sliders_by_name.find(name); + if (si != _sliders_by_name.end()) { + return (*si).second; + } + + EggSliderData *slider = _collection->make_slider_data(this); + slider->set_name(name); + _sliders_by_name.insert(SlidersByName::value_type(name, slider)); + _sliders.push_back(slider); + _components.push_back(slider); + return slider; +} + +/** + * Returns the estimated amount of memory, in megabytes, that will be required + * to perform the do_reparent() operation. This is used mainly be + * EggCharacterDb to decide up front whether to store this data in-RAM or on- + * disk. + */ +size_t EggCharacterData:: +estimate_db_size() const { + // Count how much memory we will need to store the interim transforms. This + // is models * joints * frames * 3 * sizeof(LMatrix4d). + size_t mj_frames = 0; + Models::const_iterator mi; + for (mi = _models.begin(); mi != _models.end(); ++mi) { + int model_index = (*mi)._model_index; + size_t num_frames = (size_t)get_num_frames(model_index); + mj_frames += num_frames * _joints.size(); + } + + // We do this operation a bit carefully, to guard against integer overflow. + size_t mb_needed = ((mj_frames * 3 / 1024) * sizeof(LMatrix4d)) / 1024; + + return mb_needed; +} + + +/** + * + */ +void EggCharacterData:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << "Character " << get_name() << ":\n"; + get_root_joint()->write(out, indent_level + 2); + + Sliders::const_iterator si; + for (si = _sliders.begin(); si != _sliders.end(); ++si) { + EggSliderData *slider = (*si); + slider->write(out, indent_level + 2); + } +} diff --git a/pandatool/src/eggcharbase/eggCharacterData.h b/pandatool/src/eggcharbase/eggCharacterData.h new file mode 100644 index 00000000..f488e335 --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterData.h @@ -0,0 +1,121 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterData.h + * @author drose + * @date 2001-02-23 + */ + +#ifndef EGGCHARACTERDATA_H +#define EGGCHARACTERDATA_H + +#include "pandatoolbase.h" + +#include "eggJointData.h" +#include "eggNode.h" +#include "eggData.h" +#include "pointerTo.h" +#include "namable.h" +#include "nameUniquifier.h" + +#include "pmap.h" + +class EggCharacterCollection; +class EggSliderData; +class EggCharacterDb; + +/** + * Represents a single character, as read and collected from several models + * and animation files. This contains a hierarchy of EggJointData nodes + * representing the skeleton, as well as a list of EggSliderData nodes + * representing the morph channels for the character. + * + * This is very similar to the Character class from Panda, in that it's + * capable of associating skeleton-morph animation channels with models and + * calculating the vertex position for each frame. To some degree, it + * duplicates the functionality of Character. However, it differs in one + * fundamental principle: it is designed to be a non-real-time operation, + * working directly on the Egg structures as they are, instead of first + * boiling the Egg data into native Panda Geom tables for real-time animation. + * Because of this, it is (a) double-precision instead of single precision, + * (b) capable of generating modified Egg files, and (c) about a hundred times + * slower than the Panda Character class. + * + * The data in this structure is normally filled in by the + * EggCharacterCollection class. + */ +class EggCharacterData : public Namable { +public: + EggCharacterData(EggCharacterCollection *collection); + virtual ~EggCharacterData(); + + void rename_char(const std::string &name); + + void add_model(int model_index, EggNode *model_root, EggData *egg_data); + INLINE int get_num_models() const; + INLINE int get_model_index(int n) const; + INLINE EggNode *get_model_root(int n) const; + INLINE EggData *get_egg_data(int n) const; + int get_num_frames(int model_index) const; + bool check_num_frames(int model_index); + double get_frame_rate(int model_index) const; + + INLINE EggJointData *get_root_joint() const; + INLINE EggJointData *find_joint(const std::string &name) const; + INLINE EggJointData *make_new_joint(const std::string &name, EggJointData *parent); + INLINE int get_num_joints() const; + INLINE EggJointData *get_joint(int n) const; + + bool do_reparent(); + void choose_optimal_hierarchy(); + + INLINE int get_num_sliders() const; + INLINE EggSliderData *get_slider(int n) const; + EggSliderData *find_slider(const std::string &name) const; + EggSliderData *make_slider(const std::string &name); + + INLINE int get_num_components() const; + INLINE EggComponentData *get_component(int n) const; + + size_t estimate_db_size() const; + + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + class Model { + public: + int _model_index; + PT(EggNode) _model_root; + PT(EggData) _egg_data; + }; + typedef pvector Models; + Models _models; + + EggCharacterCollection *_collection; + EggJointData *_root_joint; + + typedef pmap SlidersByName; + SlidersByName _sliders_by_name; + + typedef pvector Sliders; + Sliders _sliders; + + typedef pvector Joints; + Joints _joints; + + typedef pvector Components; + Components _components; + + NameUniquifier _component_names; + + friend class EggCharacterCollection; +}; + +#include "eggCharacterData.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggCharacterDb.I b/pandatool/src/eggcharbase/eggCharacterDb.I new file mode 100644 index 00000000..f530b93a --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterDb.I @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterDb.I + * @author drose + * @date 2006-10-05 + */ + +/** + * + */ +INLINE EggCharacterDb::Key:: +Key(const EggJointPointer *joint, TableType table_type, int frame) : + _joint(joint), + _table_type(table_type), + _frame(frame) +{ +} + +/** + * Provides an arbitrary unique ordering for all keys. + */ +INLINE bool EggCharacterDb::Key:: +operator < (const EggCharacterDb::Key &other) const { + if (_joint != other._joint) { + return _joint < other._joint; + } + if (_table_type != other._table_type) { + return _table_type < other._table_type; + } + return _frame < other._frame; +} diff --git a/pandatool/src/eggcharbase/eggCharacterDb.cxx b/pandatool/src/eggcharbase/eggCharacterDb.cxx new file mode 100644 index 00000000..e959499b --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterDb.cxx @@ -0,0 +1,125 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterDb.cxx + * @author drose + * @date 2006-10-05 + */ + +#include "eggCharacterDb.h" +#include "eggCharacterData.h" + +/** + * Constructs a database for storing the interim work for the indicated + * EggCharacterData. The parameter max_ram_mb indicates the maximum amount of + * RAM (in MB) that the database should consume; if it the database would + * roughly fit within this limit, it will be stored in RAM; otherwise, it will + * be written to disk (if Berkeley DB is available). + */ +EggCharacterDb:: +EggCharacterDb() { + /* +#ifdef HAVE_BDB + _db = NULL; + + _db = new Db(NULL, 0); + _db_filename = Filename::temporary("", "eggc_", ".db"); + + string os_db_filename = _db_filename.to_os_specific(); + _db->open(NULL, os_db_filename.c_str(), NULL, + DB_BTREE, DB_CREATE | DB_EXCL, 0); + + nout << "Using " << os_db_filename << " for rebuild database.\n"; +#endif // HAVE_BDB + */ +} + +/** + * + */ +EggCharacterDb:: +~EggCharacterDb() { + /* +#ifdef HAVE_BDB + if (_db != (Db *)NULL){ + _db->close(0); + delete _db; + _db = NULL; + + string os_db_filename = _db_filename.to_os_specific(); + Db rmdb(NULL, 0); + rmdb.remove(os_db_filename.c_str(), NULL, 0); + } +#endif // HAVE_BDB + */ +} + +/** + * Looks up the data for the indicated joint, type, and frame, and fills it in + * result (and returns true) if it is found. Returns false if this data has + * not been stored in the database. + */ +bool EggCharacterDb:: +get_matrix(const EggJointPointer *joint, TableType type, + int frame, LMatrix4d &mat) const { + Key key(joint, type, frame); + + /* +#ifdef HAVE_BDB + if (_db != (Db *)NULL){ + Dbt db_key(&key, sizeof(Key)); + Dbt db_data(&mat, sizeof(LMatrix4d)); + db_data.set_ulen(sizeof(LMatrix4d)); + db_data.set_flags(DB_DBT_USERMEM); + + int result = _db->get(NULL, &db_key, &db_data, 0); + if (result == DB_NOTFOUND) { + return false; + } + nassertr(result == 0, false); + return true; + } +#endif // HAVE_BDB + */ + + Table::const_iterator ti; + ti = _table.find(key); + if (ti == _table.end()) { + return false; + } + + mat = (*ti).second; + return true; +} + +/** + * Stores the matrix for the indicated joint, type, and frame in the database. + * It is an error to call this more than once for any given key combination + * (not for any technical reason, but because we don't expect this to happen). + */ +void EggCharacterDb:: +set_matrix(const EggJointPointer *joint, TableType type, + int frame, const LMatrix4d &mat) { + Key key(joint, type, frame); + + /* +#ifdef HAVE_BDB + if (_db != (Db *)NULL){ + Dbt db_key(&key, sizeof(Key)); + Dbt db_data((void *)&mat, sizeof(LMatrix4d)); + int result = _db->put(NULL, &db_key, &db_data, DB_NOOVERWRITE); + nassertv(result != DB_KEYEXIST); + nassertv(result == 0); + return; + } +#endif // HAVE_BDB + */ + + bool inserted = _table.insert(Table::value_type(key, mat)).second; + nassertv(inserted); +} diff --git a/pandatool/src/eggcharbase/eggCharacterDb.h b/pandatool/src/eggcharbase/eggCharacterDb.h new file mode 100644 index 00000000..31af55f2 --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterDb.h @@ -0,0 +1,84 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterDb.h + * @author drose + * @date 2006-10-05 + */ + +#ifndef EGGCHARACTERDB_H +#define EGGCHARACTERDB_H + +#include "pandatoolbase.h" +#include "pmap.h" + +/* +#ifdef HAVE_BDB + +// Apparently, we have to define this to make db_cxx files include the modern +// header files. +#define HAVE_CXX_STDHEADERS 1 +#include + +#endif // HAVE_BDB +*/ + +class EggJointPointer; +class LMatrix4d; + +/** + * This class is used during joint optimization or restructuring to store the + * table of interim joint computations. + * + * That is to say, this class provides an temporary data store for three + * tables of matrices per each EggJointPointer per frame. + */ +class EggCharacterDb { +public: + EggCharacterDb(); + ~EggCharacterDb(); + + enum TableType { + TT_rebuild_frame, + TT_net_frame, + TT_net_frame_inv, + }; + + bool get_matrix(const EggJointPointer *joint, TableType type, + int frame, LMatrix4d &mat) const; + void set_matrix(const EggJointPointer *joint, TableType type, + int frame, const LMatrix4d &mat); + +private: + class Key { + public: + INLINE Key(const EggJointPointer *joint, + TableType table_type, + int frame); + INLINE bool operator < (const Key &other) const; + + private: + const EggJointPointer *_joint; + TableType _table_type; + int _frame; + }; + + /* +#ifdef HAVE_BDB + Db *_db; + Filename _db_filename; +#endif // HAVE_BDB + */ + + typedef pmap Table; + Table _table; +}; + +#include "eggCharacterDb.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggCharacterFilter.cxx b/pandatool/src/eggcharbase/eggCharacterFilter.cxx new file mode 100644 index 00000000..02289dbe --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterFilter.cxx @@ -0,0 +1,108 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterFilter.cxx + * @author drose + * @date 2001-02-23 + */ + +#include "eggCharacterFilter.h" +#include "eggCharacterCollection.h" +#include "eggCharacterData.h" + + +/** + * + */ +EggCharacterFilter:: +EggCharacterFilter() : EggMultiFilter(false) { + _collection = nullptr; + + _force_initial_rest_frame = false; +} + +/** + * + */ +EggCharacterFilter:: +~EggCharacterFilter() { + delete _collection; +} + +/** + * + */ +void EggCharacterFilter:: +add_fixrest_option() { + add_option + ("fixrest", "", 30, + "Specify this to force all the initial rest frames of the various " + "model files to the same value as the first model specified. This " + "is a fairly drastic way to repair models whose initial rest frame " + "values are completely bogus, but should not be performed when the " + "input models are correct.", + &EggCharacterFilter::dispatch_none, &_force_initial_rest_frame); +} + + +/** + * + */ +bool EggCharacterFilter:: +post_command_line() { + if (_collection == nullptr) { + _collection = make_collection(); + } + + if (!EggMultiFilter::post_command_line()) { + return false; + } + + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + EggData *data = (*ei); + + if (_collection->add_egg(data) < 0) { + nout << data->get_egg_filename().get_basename() + << " does not contain a character model or animation channel.\n"; + return false; + } + } + + _collection->check_errors(nout, _force_initial_rest_frame); + + return true; +} + +/** + * Writes out all of the egg files in the _eggs vector, to the output + * directory if one is specified, or over the input files if -inplace was + * specified. + */ +void EggCharacterFilter:: +write_eggs() { + // Optimize (that is, collapse redudant nodes) in all of the characters' + // joint tables before writing them out. + int num_characters = _collection->get_num_characters(); + for (int i = 0; i < num_characters; i++) { + EggCharacterData *char_data = _collection->get_character(i); + char_data->get_root_joint()->optimize(); + } + + EggMultiFilter::write_eggs(); +} + +/** + * Allocates and returns a new EggCharacterCollection structure. This is + * primarily intended as a hook so derived classes can customize the type of + * EggCharacterCollection object used to represent the character information. + */ +EggCharacterCollection *EggCharacterFilter:: +make_collection() { + return new EggCharacterCollection; +} diff --git a/pandatool/src/eggcharbase/eggCharacterFilter.h b/pandatool/src/eggcharbase/eggCharacterFilter.h new file mode 100644 index 00000000..c58536fa --- /dev/null +++ b/pandatool/src/eggcharbase/eggCharacterFilter.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCharacterFilter.h + * @author drose + * @date 2001-02-23 + */ + +#ifndef EGGCHARACTERFILTER_H +#define EGGCHARACTERFILTER_H + +#include "pandatoolbase.h" + +#include "eggMultiFilter.h" + +class EggCharacterData; +class EggCharacterCollection; + +/** + * This is the base class for a family of programs that operate on a number of + * character models and their associated animation files together. It reads + * in a number of egg files, any combination of model files or character files + * which must all represent the same character skeleton, and maintains a + * single hierarchy of joints and sliders that may be operated on before + * writing the files back out. + */ +class EggCharacterFilter : public EggMultiFilter { +public: + EggCharacterFilter(); + virtual ~EggCharacterFilter(); + + void add_fixrest_option(); + +protected: + virtual bool post_command_line(); + virtual void write_eggs(); + + virtual EggCharacterCollection *make_collection(); + + EggCharacterCollection *_collection; + bool _force_initial_rest_frame; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggComponentData.I b/pandatool/src/eggcharbase/eggComponentData.I new file mode 100644 index 00000000..e0064948 --- /dev/null +++ b/pandatool/src/eggcharbase/eggComponentData.I @@ -0,0 +1,47 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggComponentData.I + * @author drose + * @date 2001-02-26 + */ + +/** + * Returns the maximum number of back pointers this component may have. The + * component may store a back pointer for models indexed 0 .. num_models - 1. + * You must call has_model() on each model index to confirm whether a + * particular model in that range has a back pointer. + */ +INLINE int EggComponentData:: +get_num_models() const { + return _back_pointers.size(); +} + +/** + * Returns true if the component has a back pointer to an egg file somewhere + * for the indicated model, false otherwise. + */ +INLINE bool EggComponentData:: +has_model(int model_index) const { + if (model_index >= 0 && model_index < (int)_back_pointers.size()) { + return _back_pointers[model_index] != nullptr; + } + return false; +} + +/** + * Returns the back pointer to an egg file for the indicated model if it + * exists, or NULL if it does not. + */ +INLINE EggBackPointer *EggComponentData:: +get_model(int model_index) const { + if (model_index >= 0 && model_index < (int)_back_pointers.size()) { + return _back_pointers[model_index]; + } + return nullptr; +} diff --git a/pandatool/src/eggcharbase/eggComponentData.cxx b/pandatool/src/eggcharbase/eggComponentData.cxx new file mode 100644 index 00000000..ab57e5bd --- /dev/null +++ b/pandatool/src/eggcharbase/eggComponentData.cxx @@ -0,0 +1,128 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggComponentData.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggComponentData.h" +#include "eggBackPointer.h" +#include "nameUniquifier.h" + +#include "indent.h" + +TypeHandle EggComponentData::_type_handle; + + +/** + * + */ +EggComponentData:: +EggComponentData(EggCharacterCollection *collection, + EggCharacterData *char_data) : + _collection(collection), + _char_data(char_data) +{ +} + +/** + * + */ +EggComponentData:: +~EggComponentData() { + for (EggBackPointer *back : _back_pointers) { + delete back; + } +} + +/** + * Adds the indicated name to the set of names that this component can be + * identified with. If this is the first name added, it becomes the primary + * name of the component; later names added do not replace the primary name, + * but do get added to the list of names that will be accepted by + * matched_name(). + */ +void EggComponentData:: +add_name(const std::string &name, NameUniquifier &uniquifier) { + if (_names.insert(name).second) { + // This is a new name for this component. + if (!has_name()) { + set_name(uniquifier.add_name(name)); + if (get_name() != name) { + nout << "Warning: renamed " << name << " to " << get_name() + << " to avoid naming conflict.\n"; + } + } + } +} + +/** + * Returns true if the indicated name matches any name that was ever matched + * with this particular joint, false otherwise. + */ +bool EggComponentData:: +matches_name(const std::string &name) const { + if (name == get_name()) { + return true; + } + return (_names.find(name) != _names.end()); +} + +/** + * Returns the number of frames of animation for this particular component in + * the indicated model. + */ +int EggComponentData:: +get_num_frames(int model_index) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return 0; + } + return back->get_num_frames(); +} + +/** + * Extends the number of frames in the indicated model (presumably an + * animation table model) to the given number. + */ +void EggComponentData:: +extend_to(int model_index, int num_frames) const { + EggBackPointer *back = get_model(model_index); + nassertv(back != nullptr); + back->extend_to(num_frames); +} + +/** + * Returns the number of frames of animation for this particular component in + * the indicated model. + */ +double EggComponentData:: +get_frame_rate(int model_index) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return 0.0; + } + return back->get_frame_rate(); +} + +/** + * Sets the back_pointer associated with the given model_index. + */ +void EggComponentData:: +set_model(int model_index, EggBackPointer *back) { + while ((int)_back_pointers.size() <= model_index) { + _back_pointers.push_back(nullptr); + } + + if (_back_pointers[model_index] != nullptr) { + nout << "Warning: deleting old back pointer.\n"; + delete _back_pointers[model_index]; + } + _back_pointers[model_index] = back; +} diff --git a/pandatool/src/eggcharbase/eggComponentData.h b/pandatool/src/eggcharbase/eggComponentData.h new file mode 100644 index 00000000..b54c20ff --- /dev/null +++ b/pandatool/src/eggcharbase/eggComponentData.h @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggComponentData.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGCOMPONENTDATA_H +#define EGGCOMPONENTDATA_H + +#include "pandatoolbase.h" + +#include "eggObject.h" +#include "namable.h" +#include "pset.h" + +class EggCharacterCollection; +class EggCharacterData; +class EggBackPointer; +class NameUniquifier; + +/** + * This is the base class of both EggJointData and EggSliderData. It + * represents a single component of a character, either a joint or a slider, + * along with back pointers to the references to this component in all model + * and animation egg files read. + */ +class EggComponentData : public EggObject, public Namable { +public: + EggComponentData(EggCharacterCollection *collection, + EggCharacterData *char_data); + virtual ~EggComponentData(); + + void add_name(const std::string &name, NameUniquifier &uniquifier); + bool matches_name(const std::string &name) const; + + int get_num_frames(int model_index) const; + void extend_to(int model_index, int num_frames) const; + double get_frame_rate(int model_index) const; + + virtual void add_back_pointer(int model_index, EggObject *egg_object)=0; + virtual void write(std::ostream &out, int indent_level = 0) const=0; + + INLINE int get_num_models() const; + INLINE bool has_model(int model_index) const; + INLINE EggBackPointer *get_model(int model_index) const; + void set_model(int model_index, EggBackPointer *back); + +protected: + + // This points back to all the egg structures that reference this particular + // table or slider. + typedef pvector BackPointers; + BackPointers _back_pointers; + + typedef pset Names; + Names _names; + + EggCharacterCollection *_collection; + EggCharacterData *_char_data; + + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggObject::init_type(); + register_type(_type_handle, "EggComponentData", + EggObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "eggComponentData.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggJointData.I b/pandatool/src/eggcharbase/eggJointData.I new file mode 100644 index 00000000..c8b81967 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointData.I @@ -0,0 +1,93 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointData.I + * @author drose + * @date 2001-02-23 + */ + +/** + * + */ +INLINE EggJointData *EggJointData:: +get_parent() const { + return _parent; +} + +/** + * + */ +INLINE int EggJointData:: +get_num_children() const { + return _children.size(); +} + +/** + * + */ +INLINE EggJointData *EggJointData:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), nullptr); + return _children[n]; +} + +/** + * Returns the first descendent joint found with the indicated name, or NULL + * if no joint has that name. + */ +INLINE EggJointData *EggJointData:: +find_joint(const std::string &name) { + EggJointData *joint = find_joint_exact(name); + if (joint == nullptr) { + joint = find_joint_matches(name); + } + return joint; +} + + +/** + * Returns true if the joint knows its rest frame, false otherwise. In + * general, this will be true as long as the joint is included in at least one + * model file, or false if it appears only in animation files. + */ +INLINE bool EggJointData:: +has_rest_frame() const { + return _has_rest_frame; +} + +/** + * Returns true if the rest frames for different models differ in their + * initial value. This is not technically an error, but it is unusual enough + * to be suspicious. + */ +INLINE bool EggJointData:: +rest_frames_differ() const { + return _rest_frames_differ; +} + +/** + * Returns the rest frame of the joint. This is the matrix value that appears + * for the joint in each model file; it should be the same transform in each + * model. + */ +INLINE const LMatrix4d &EggJointData:: +get_rest_frame() const { + nassertr(has_rest_frame(), LMatrix4d::ident_mat()); + return _rest_frame; +} + +/** + * Indicates an intention to change the parent of this joint to the indicated + * joint, or NULL to remove it from the hierarchy. The joint is not + * reparented immediately, but rather all of the joints are reparented at once + * when do_reparent() is called. + */ +INLINE void EggJointData:: +reparent_to(EggJointData *new_parent) { + _new_parent = new_parent; +} diff --git a/pandatool/src/eggcharbase/eggJointData.cxx b/pandatool/src/eggcharbase/eggJointData.cxx new file mode 100644 index 00000000..ebf656b1 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointData.cxx @@ -0,0 +1,769 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointData.cxx + * @author drose + * @date 2001-02-23 + */ + +#include "eggJointData.h" + +#include "eggCharacterDb.h" +#include "eggJointNodePointer.h" +#include "eggMatrixTablePointer.h" +#include "pvector.h" +#include "dcast.h" +#include "eggGroup.h" +#include "eggTable.h" +#include "indent.h" +#include "fftCompressor.h" +#include "zStream.h" + +using std::string; + +TypeHandle EggJointData::_type_handle; + + +/** + * + */ +EggJointData:: +EggJointData(EggCharacterCollection *collection, + EggCharacterData *char_data) : + EggComponentData(collection, char_data) +{ + _parent = nullptr; + _new_parent = nullptr; + _has_rest_frame = false; + _rest_frames_differ = false; +} + +/** + * Returns the local transform matrix corresponding to this joint position in + * the nth frame in the indicated model. + */ +LMatrix4d EggJointData:: +get_frame(int model_index, int n) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return LMatrix4d::ident_mat(); + } + + EggJointPointer *joint; + DCAST_INTO_R(joint, back, LMatrix4d::ident_mat()); + + return joint->get_frame(n); +} + +/** + * Returns the complete transform from the root corresponding to this joint + * position in the nth frame in the indicated model. + */ +LMatrix4d EggJointData:: +get_net_frame(int model_index, int n, EggCharacterDb &db) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return LMatrix4d::ident_mat(); + } + + EggJointPointer *joint; + DCAST_INTO_R(joint, back, LMatrix4d::ident_mat()); + + LMatrix4d mat; + if (!db.get_matrix(joint, EggCharacterDb::TT_net_frame, n, mat)) { + // Compute this frame's net, and stuff it in. + mat = get_frame(model_index, n); + if (_parent != nullptr) { + mat = mat * _parent->get_net_frame(model_index, n, db); + } + db.set_matrix(joint, EggCharacterDb::TT_net_frame, n, mat); + } + + return mat; +} + +/** + * Returns the inverse of get_net_frame(). + */ +LMatrix4d EggJointData:: +get_net_frame_inv(int model_index, int n, EggCharacterDb &db) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return LMatrix4d::ident_mat(); + } + + EggJointPointer *joint; + DCAST_INTO_R(joint, back, LMatrix4d::ident_mat()); + + LMatrix4d mat; + if (!db.get_matrix(joint, EggCharacterDb::TT_net_frame_inv, n, mat)) { + // Compute this frame's net inverse, and stuff it in. + LMatrix4d mat = get_net_frame(model_index, n, db); + mat.invert_in_place(); + db.set_matrix(joint, EggCharacterDb::TT_net_frame_inv, n, mat); + } + + return mat; +} + +/** + * Forces all of the joints to have the same rest frame value as the first + * joint read in. This is a drastic way to repair models whose rest frame + * values are completely bogus, but should not be performed on models that are + * otherwise correct. + */ +void EggJointData:: +force_initial_rest_frame() { + if (!has_rest_frame()) { + return; + } + int num_models = get_num_models(); + for (int model_index = 0; model_index < num_models; model_index++) { + if (has_model(model_index)) { + EggJointPointer *joint; + DCAST_INTO_V(joint, get_model(model_index)); + if (joint->is_of_type(EggJointNodePointer::get_class_type())) { + joint->set_frame(0, get_rest_frame()); + } + } + } + _rest_frames_differ = false; +} + +/** + * Moves the vertices assigned to this joint into the indicated joint, without + * changing their weight assignments. + */ +void EggJointData:: +move_vertices_to(EggJointData *new_owner) { + int num_models = get_num_models(); + + if (new_owner == nullptr) { + for (int model_index = 0; model_index < num_models; model_index++) { + if (has_model(model_index)) { + EggJointPointer *joint; + DCAST_INTO_V(joint, get_model(model_index)); + joint->move_vertices_to(nullptr); + } + } + } else { + for (int model_index = 0; model_index < num_models; model_index++) { + if (has_model(model_index) && new_owner->has_model(model_index)) { + EggJointPointer *joint, *new_joint; + DCAST_INTO_V(joint, get_model(model_index)); + DCAST_INTO_V(new_joint, new_owner->get_model(model_index)); + joint->move_vertices_to(new_joint); + } + } + } +} + +/** + * Computes a score >= 0 reflecting the similarity of the current joint's + * animation (in world space) to that of the indicated potential parent joint + * (in world space). The lower the number, the more similar the motion, and + * the more suitable is the proposed parent-child relationship. Returns -1 if + * there is an error. + */ +int EggJointData:: +score_reparent_to(EggJointData *new_parent, EggCharacterDb &db) { + if (!FFTCompressor::is_compression_available()) { + // If we don't have compression compiled in, we can't meaningfully score + // the joints. + return -1; + } + + // First, build up a big array of the new transforms this joint would + // receive in all frames of all models, were it reparented to the indicated + // joint. + vector_stdfloat i, j, k, a, b, c, x, y, z; + pvector hprs; + int num_rows = 0; + + int num_models = get_num_models(); + for (int model_index = 0; model_index < num_models; model_index++) { + EggBackPointer *back = get_model(model_index); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_R(joint, back, false); + + int num_frames = get_num_frames(model_index); + for (int n = 0; n < num_frames; n++) { + LMatrix4d transform; + if (_parent == new_parent) { + // We already have this parent. + transform = LMatrix4d::ident_mat(); + + } else if (_parent == nullptr) { + // We are moving from outside the joint hierarchy to within it. + transform = new_parent->get_net_frame_inv(model_index, n, db); + + } else if (new_parent == nullptr) { + // We are moving from within the hierarchy to outside it. + transform = _parent->get_net_frame(model_index, n, db); + + } else { + // We are changing parents within the hierarchy. + transform = + _parent->get_net_frame(model_index, n, db) * + new_parent->get_net_frame_inv(model_index, n, db); + } + + transform = joint->get_frame(n) * transform; + LVecBase3d scale, shear, hpr, translate; + if (!decompose_matrix(transform, scale, shear, hpr, translate)) { + // Invalid transform. + return -1; + } + i.push_back(scale[0]); + j.push_back(scale[1]); + k.push_back(scale[2]); + a.push_back(shear[0]); + b.push_back(shear[1]); + c.push_back(shear[2]); + hprs.push_back(LCAST(PN_stdfloat, hpr)); + x.push_back(translate[0]); + y.push_back(translate[1]); + z.push_back(translate[2]); + num_rows++; + } + } + } + + if (num_rows == 0) { + // No data, no score. + return -1; + } + + // Now, we derive a score, by the simple expedient of using the + // FFTCompressor to compress the generated transforms, and measuring the + // length of the resulting bitstream. + FFTCompressor compressor; + Datagram dg; + compressor.write_reals(dg, &i[0], num_rows); + compressor.write_reals(dg, &j[0], num_rows); + compressor.write_reals(dg, &k[0], num_rows); + compressor.write_reals(dg, &a[0], num_rows); + compressor.write_reals(dg, &b[0], num_rows); + compressor.write_reals(dg, &c[0], num_rows); + compressor.write_hprs(dg, &hprs[0], num_rows); + compressor.write_reals(dg, &x[0], num_rows); + compressor.write_reals(dg, &y[0], num_rows); + compressor.write_reals(dg, &z[0], num_rows); + + +#ifndef HAVE_ZLIB + return dg.get_length(); + +#else + // The FFTCompressor does minimal run-length encoding, but to really get an + // accurate measure we should zlib-compress the resulting stream. + std::ostringstream sstr; + OCompressStream zstr(&sstr, false); + zstr.write((const char *)dg.get_data(), dg.get_length()); + zstr.flush(); + return sstr.str().length(); +#endif +} + +/** + * Calls do_rebuild() on all models, and recursively on all joints at this + * node and below. Returns true if all models returned true, false otherwise. + */ +bool EggJointData:: +do_rebuild_all(EggCharacterDb &db) { + bool all_ok = true; + + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_R(joint, back, false); + if (!joint->do_rebuild(db)) { + all_ok = false; + } + } + } + + Children::iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + if (!child->do_rebuild_all(db)) { + all_ok = false; + } + } + + return all_ok; +} + +/** + * Calls optimize() on all models, and recursively on all joints at this node + * and below. + */ +void EggJointData:: +optimize() { + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + joint->optimize(); + } + } + + Children::iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + child->optimize(); + } +} + +/** + * Calls expose() on all models for this joint, but does not recurse + * downwards. + */ +void EggJointData:: +expose(EggGroup::DCSType dcs_type) { + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + joint->expose(dcs_type); + } + } +} + +/** + * Calls zero_channels() on all models for this joint, but does not recurse + * downwards. + */ +void EggJointData:: +zero_channels(const string &components) { + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + joint->zero_channels(components); + } + } +} + +/** + * Calls quantize_channels() on all models for this joint, and then recurses + * downwards to all joints below. + */ +void EggJointData:: +quantize_channels(const string &components, double quantum) { + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + joint->quantize_channels(components, quantum); + } + } + + Children::iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + child->quantize_channels(components, quantum); + } +} + +/** + * Applies the pose from the indicated frame of the indicated source + * model_index as the initial pose for this joint, and does this recursively + * on all joints below. + */ +void EggJointData:: +apply_default_pose(int source_model, int frame) { + if (has_model(source_model)) { + EggJointPointer *source_joint; + DCAST_INTO_V(source_joint, _back_pointers[source_model]); + BackPointers::iterator bpi; + for (bpi = _back_pointers.begin(); bpi != _back_pointers.end(); ++bpi) { + EggBackPointer *back = (*bpi); + if (back != nullptr) { + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + joint->apply_default_pose(source_joint, frame); + } + } + } + + Children::iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + child->apply_default_pose(source_model, frame); + } +} + +/** + * Adds the indicated model joint or anim table to the data. + */ +void EggJointData:: +add_back_pointer(int model_index, EggObject *egg_object) { + nassertv(egg_object != nullptr); + if (egg_object->is_of_type(EggGroup::get_class_type())) { + // It must be a . + EggJointNodePointer *joint = new EggJointNodePointer(egg_object); + set_model(model_index, joint); + if (!_has_rest_frame) { + _rest_frame = joint->get_frame(0); + _has_rest_frame = true; + + } else { + // If this new node doesn't come within an acceptable tolerance of our + // first reading of this joint's rest frame, set a warning flag. + if (!_rest_frame.almost_equal(joint->get_frame(0), 0.0001)) { + _rest_frames_differ = true; + } + } + + } else if (egg_object->is_of_type(EggTable::get_class_type())) { + // It's a
with an "xform" child beneath it. + EggMatrixTablePointer *xform = new EggMatrixTablePointer(egg_object); + set_model(model_index, xform); + + } else { + nout << "Invalid object added to joint for back pointer.\n"; + } +} + +/** + * + */ +void EggJointData:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << "Joint " << get_name() + << " (models:"; + int num_models = get_num_models(); + for (int model_index = 0; model_index < num_models; model_index++) { + if (has_model(model_index)) { + out << " " << model_index; + } + } + out << ") {\n"; + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write(out, indent_level + 2); + } + + indent(out, indent_level) << "}\n"; +} + +/** + * Clears out the _children vector in preparation for refilling it from the + * _new_parent information. + */ +void EggJointData:: +do_begin_reparent() { + _got_new_parent_depth = false; + _children.clear(); +} + +/** + * Calculates the number of joints above this joint in its intended position, + * as specified by a recent call to reparent_to(), and also checks for a cycle + * in the new parent chain. Returns true if a cycle is detected, and false + * otherwise. If a cycle is not detected, _new_parent_depth can be consulted + * for the depth in the new hierarchy. + * + * This is used by EggCharacterData::do_reparent() to determine the order in + * which to apply the reparent operations. It should be called after + * do_begin_reparent(). + */ +bool EggJointData:: +calc_new_parent_depth(pset &chain) { + if (_got_new_parent_depth) { + return false; + } + if (_new_parent == nullptr) { + // Here's the top of the new hierarchy. + _got_new_parent_depth = true; + _new_parent_depth = 0; + return false; + } + if (!chain.insert(this).second) { + // We've already visited this joint; that means there's a cycle. + return true; + } + bool cycle = _new_parent->calc_new_parent_depth(chain); + _new_parent_depth = _new_parent->_new_parent_depth + 1; + return cycle; +} + +/** + * Eliminates any cached values before beginning a walk through all the joints + * for do_compute_reparent(), for a given model/frame. + */ +void EggJointData:: +do_begin_compute_reparent() { + _got_new_net_frame = false; + _got_new_net_frame_inv = false; + _computed_reparent = false; +} + +/** + * Prepares the reparent operation by computing a new transform for each frame + * of each model, designed to keep the net transform the same when the joint + * is moved to its new parent. Returns true on success, false on failure. + */ +bool EggJointData:: +do_compute_reparent(int model_index, int n, EggCharacterDb &db) { + if (_computed_reparent) { + // We've already done this joint. This is possible because we have to + // recursively compute joints upwards, so we might visit the same joint + // more than once. + return _computed_ok; + } + _computed_reparent = true; + + if (_parent == _new_parent) { + // Trivial (and most common) case: we are not moving the joint. No + // recomputation necessary. + _computed_ok = true; + return true; + } + + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + // This joint doesn't have any data to modify. + _computed_ok = true; + return true; + } + + EggJointPointer *joint; + DCAST_INTO_R(joint, back, false); + + LMatrix4d transform; + if (_parent == nullptr) { + // We are moving from outside the joint hierarchy to within it. + transform = _new_parent->get_new_net_frame_inv(model_index, n, db); + + } else if (_new_parent == nullptr) { + // We are moving from within the hierarchy to outside it. + transform = _parent->get_net_frame(model_index, n, db); + + } else { + // We are changing parents within the hierarchy. + transform = + _parent->get_net_frame(model_index, n, db) * + _new_parent->get_new_net_frame_inv(model_index, n, db); + } + + db.set_matrix(joint, EggCharacterDb::TT_rebuild_frame, n, + joint->get_frame(n) * transform); + _computed_ok = true; + + return _computed_ok; +} + +/** + * Calls do_rebuild() on the joint for the indicated model index. Returns + * true on success, false on failure (false shouldn't be possible). + */ +bool EggJointData:: +do_joint_rebuild(int model_index, EggCharacterDb &db) { + bool all_ok = true; + + EggJointPointer *parent_joint = nullptr; + if (_new_parent != nullptr && _new_parent->has_model(model_index)) { + DCAST_INTO_R(parent_joint, _new_parent->get_model(model_index), false); + } + + if (has_model(model_index)) { + EggJointPointer *joint; + DCAST_INTO_R(joint, get_model(model_index), false); + if (!joint->do_rebuild(db)) { + all_ok = false; + } + } + + return all_ok; +} + +/** + * Performs the actual reparenting operation by removing all of the old + * children and replacing them with the set of new children. + */ +void EggJointData:: +do_finish_reparent() { + int num_models = get_num_models(); + for (int model_index = 0; model_index < num_models; model_index++) { + EggJointPointer *parent_joint = nullptr; + if (_new_parent != nullptr && _new_parent->has_model(model_index)) { + DCAST_INTO_V(parent_joint, _new_parent->get_model(model_index)); + } + + if (has_model(model_index)) { + EggJointPointer *joint; + DCAST_INTO_V(joint, get_model(model_index)); + joint->do_finish_reparent(parent_joint); + } + } + + _parent = _new_parent; + if (_parent != nullptr) { + _parent->_children.push_back(this); + } +} + +/** + * Creates a new joint as a child of this joint and returns it. This is + * intended to be called only from EggCharacterData::make_new_joint(). + */ +EggJointData *EggJointData:: +make_new_joint(const string &name) { + EggJointData *child = new EggJointData(_collection, _char_data); + child->set_name(name); + child->_parent = this; + child->_new_parent = this; + _children.push_back(child); + + // Also create new back pointers in each of the models. + int num_models = get_num_models(); + for (int i = 0; i < num_models; i++) { + if (has_model(i)) { + EggJointPointer *joint; + DCAST_INTO_R(joint, get_model(i), nullptr); + EggJointPointer *new_joint = joint->make_new_joint(name); + child->set_model(i, new_joint); + } + } + + return child; +} + +/** + * The recursive implementation of find_joint, this flavor searches + * recursively for an exact match of the preferred joint name. + */ +EggJointData *EggJointData:: +find_joint_exact(const string &name) { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + if (child->get_name() == name) { + return child; + } + EggJointData *result = child->find_joint_exact(name); + if (result != nullptr) { + return result; + } + } + + return nullptr; +} + +/** + * The recursive implementation of find_joint, this flavor searches + * recursively for any acceptable match. + */ +EggJointData *EggJointData:: +find_joint_matches(const string &name) { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + EggJointData *child = (*ci); + if (child->matches_name(name)) { + return child; + } + EggJointData *result = child->find_joint_matches(name); + if (result != nullptr) { + return result; + } + } + + return nullptr; +} + +/** + * Returns true if this joint is an ancestor of the indicated joint, in the + * "new" hierarchy (that is, the one defined by _new_parent, as set by + * reparent_to() before do_finish_reparent() is called). + */ +bool EggJointData:: +is_new_ancestor(EggJointData *child) const { + if (child == this) { + return true; + } + + if (child->_new_parent == nullptr) { + return false; + } + + return is_new_ancestor(child->_new_parent); +} + +/** + * Similar to get_net_frame(), but computed for the prospective new parentage + * of the node, before do_finish_reparent() is called. This is generally + * useful only when called within do_compute_reparent(). + */ +const LMatrix4d &EggJointData:: +get_new_net_frame(int model_index, int n, EggCharacterDb &db) { + if (!_got_new_net_frame) { + _new_net_frame = get_new_frame(model_index, n, db); + if (_new_parent != nullptr) { + _new_net_frame = _new_net_frame * _new_parent->get_new_net_frame(model_index, n, db); + } + _got_new_net_frame = true; + } + return _new_net_frame; +} + +/** + * Returns the inverse of get_new_net_frame(). + */ +const LMatrix4d &EggJointData:: +get_new_net_frame_inv(int model_index, int n, EggCharacterDb &db) { + if (!_got_new_net_frame_inv) { + _new_net_frame_inv.invert_from(get_new_frame(model_index, n, db)); + if (_new_parent != nullptr) { + _new_net_frame_inv = _new_parent->get_new_net_frame_inv(model_index, n, db) * _new_net_frame_inv; + } + _got_new_net_frame_inv = true; + } + return _new_net_frame_inv; +} + +/** + * Returns the local transform matrix corresponding to this joint position in + * the nth frame in the indicated model, as it will be when + * do_finish_reparent() is called. + */ +LMatrix4d EggJointData:: +get_new_frame(int model_index, int n, EggCharacterDb &db) { + do_compute_reparent(model_index, n, db); + + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return LMatrix4d::ident_mat(); + } + + EggJointPointer *joint; + DCAST_INTO_R(joint, back, LMatrix4d::ident_mat()); + + LMatrix4d mat; + if (!db.get_matrix(joint, EggCharacterDb::TT_rebuild_frame, n, mat)) { + // No rebuild frame; return the regular frame. + return joint->get_frame(n); + } + + // Return the rebuild frame, as computed. + return mat; +} diff --git a/pandatool/src/eggcharbase/eggJointData.h b/pandatool/src/eggcharbase/eggJointData.h new file mode 100644 index 00000000..bf9cf7b3 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointData.h @@ -0,0 +1,126 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointData.h + * @author drose + * @date 2001-02-23 + */ + +#ifndef EGGJOINTDATA_H +#define EGGJOINTDATA_H + +#include "pandatoolbase.h" +#include "eggComponentData.h" +#include "eggGroup.h" +#include "luse.h" +#include "pset.h" + +class EggCharacterDb; + +/** + * This is one node of a hierarchy of EggJointData nodes, each of which + * represents a single joint of the character hierarchy across all loaded + * files: the various models, the LOD's of each model, and the various + * animation channel files. + */ +class EggJointData : public EggComponentData { +public: + EggJointData(EggCharacterCollection *collection, + EggCharacterData *char_data); + + INLINE EggJointData *get_parent() const; + INLINE int get_num_children() const; + INLINE EggJointData *get_child(int n) const; + INLINE EggJointData *find_joint(const std::string &name); + + LMatrix4d get_frame(int model_index, int n) const; + LMatrix4d get_net_frame(int model_index, int n, EggCharacterDb &db) const; + LMatrix4d get_net_frame_inv(int model_index, int n, EggCharacterDb &db) const; + + INLINE bool has_rest_frame() const; + INLINE bool rest_frames_differ() const; + INLINE const LMatrix4d &get_rest_frame() const; + void force_initial_rest_frame(); + + INLINE void reparent_to(EggJointData *new_parent); + void move_vertices_to(EggJointData *new_owner); + int score_reparent_to(EggJointData *new_parent, EggCharacterDb &db); + + bool do_rebuild_all(EggCharacterDb &db); + void optimize(); + void expose(EggGroup::DCSType dcs_type = EggGroup::DC_default); + void zero_channels(const std::string &components); + void quantize_channels(const std::string &components, double quantum); + void apply_default_pose(int source_model, int frame); + + virtual void add_back_pointer(int model_index, EggObject *egg_object); + virtual void write(std::ostream &out, int indent_level = 0) const; + +protected: + void do_begin_reparent(); + bool calc_new_parent_depth(pset &chain); + void do_begin_compute_reparent(); + bool do_compute_reparent(int model_index, int n, EggCharacterDb &db); + bool do_joint_rebuild(int model_index, EggCharacterDb &db); + void do_finish_reparent(); + +private: + EggJointData *make_new_joint(const std::string &name); + EggJointData *find_joint_exact(const std::string &name); + EggJointData *find_joint_matches(const std::string &name); + + bool is_new_ancestor(EggJointData *child) const; + const LMatrix4d &get_new_net_frame(int model_index, int n, EggCharacterDb &db); + const LMatrix4d &get_new_net_frame_inv(int model_index, int n, EggCharacterDb &db); + LMatrix4d get_new_frame(int model_index, int n, EggCharacterDb &db); + + bool _has_rest_frame; + bool _rest_frames_differ; + LMatrix4d _rest_frame; + + // These are used to cache the above results for optimizing + // do_compute_reparent(). + LMatrix4d _new_net_frame, _new_net_frame_inv; + bool _got_new_net_frame, _got_new_net_frame_inv; + bool _computed_reparent; + bool _computed_ok; + +protected: + typedef pvector Children; + Children _children; + EggJointData *_parent; + EggJointData *_new_parent; + int _new_parent_depth; + bool _got_new_parent_depth; + + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggComponentData::init_type(); + register_type(_type_handle, "EggJointData", + EggComponentData::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class EggCharacterCollection; + friend class EggCharacterData; + friend class OrderJointsByNewDepth; +}; + +#include "eggJointData.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggJointNodePointer.cxx b/pandatool/src/eggcharbase/eggJointNodePointer.cxx new file mode 100644 index 00000000..23bf7ea4 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointNodePointer.cxx @@ -0,0 +1,209 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointNodePointer.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggJointNodePointer.h" + +#include "dcast.h" +#include "eggCharacterDb.h" +#include "eggGroup.h" +#include "eggObject.h" +#include "pointerTo.h" + + +TypeHandle EggJointNodePointer::_type_handle; + +/** + * + */ +EggJointNodePointer:: +EggJointNodePointer(EggObject *object) { + _joint = DCAST(EggGroup, object); + + if (_joint != nullptr && _joint->is_joint()) { + // Quietly insist that the joint has a transform, for neatness. If it + // does not, give it the identity transform. + if (!_joint->has_transform()) { + _joint->set_transform3d(LMatrix4d::ident_mat()); + } + } +} + +/** + * Returns the number of frames of animation for this particular joint. + * + * In the case of a EggJointNodePointer, which just stores a pointer to a + * entry for a character model (not an animation table), there is + * always exactly one frame: the rest pose. + */ +int EggJointNodePointer:: +get_num_frames() const { + return 1; +} + +/** + * Returns the transform matrix corresponding to this joint position in the + * nth frame. + * + * In the case of a EggJointNodePointer, which just stores a pointer to a + * entry for a character model (not an animation table), there is + * always exactly one frame: the rest pose. + */ +LMatrix4d EggJointNodePointer:: +get_frame(int n) const { + nassertr(n == 0, LMatrix4d::ident_mat()); + return _joint->get_transform3d(); +} + +/** + * Sets the transform matrix corresponding to this joint position in the nth + * frame. + * + * In the case of a EggJointNodePointer, which just stores a pointer to a + * entry for a character model (not an animation table), there is + * always exactly one frame: the rest pose. + */ +void EggJointNodePointer:: +set_frame(int n, const LMatrix4d &mat) { + nassertv(n == 0); + _joint->set_transform3d(mat); +} + +/** + * Performs the actual reparenting operation by removing the node from its old + * parent and associating it with its new parent, if any. + */ +void EggJointNodePointer:: +do_finish_reparent(EggJointPointer *new_parent) { + if (new_parent == nullptr) { + // No new parent; unparent the joint. + EggGroupNode *egg_parent = _joint->get_parent(); + if (egg_parent != nullptr) { + egg_parent->remove_child(_joint.p()); + egg_parent->steal_children(*_joint); + } + + } else { + // Reparent the joint to its new parent (implicitly unparenting it from + // its previous parent). + EggJointNodePointer *new_node = DCAST(EggJointNodePointer, new_parent); + if (new_node->_joint != _joint->get_parent()) { + new_node->_joint->add_child(_joint.p()); + } + } +} + +/** + * Moves the vertices assigned to this joint into the other joint (which + * should be of the same type). + */ +void EggJointNodePointer:: +move_vertices_to(EggJointPointer *new_joint) { + if (new_joint == nullptr) { + _joint->unref_all_vertices(); + + } else { + EggJointNodePointer *new_node; + DCAST_INTO_V(new_node, new_joint); + + new_node->_joint->steal_vrefs(_joint); + } +} + +/** + * Rebuilds the entire table all at once, based on the frames added by + * repeated calls to add_rebuild_frame() since the last call to + * begin_rebuild(). + * + * Until do_rebuild() is called, the animation table is not changed. + * + * The return value is true if all frames are acceptable, or false if there is + * some problem. + */ +bool EggJointNodePointer:: +do_rebuild(EggCharacterDb &db) { + LMatrix4d mat; + if (!db.get_matrix(this, EggCharacterDb::TT_rebuild_frame, 0, mat)) { + // No rebuild frame; this is OK. + return true; + } + + _joint->set_transform3d(mat); + + // We shouldn't have a frame 1. + nassertr(!db.get_matrix(this, EggCharacterDb::TT_rebuild_frame, 1, mat), false); + + return true; +} + +/** + * Flags the joint with the indicated DCS flag so that it will be loaded as a + * separate node in the player. + */ +void EggJointNodePointer:: +expose(EggGroup::DCSType dcs_type) { + if (_joint != nullptr) { + _joint->set_dcs_type(dcs_type); + } +} + +/** + * Applies the pose from the indicated frame of the indicated source joint as + * the initial pose for this joint. + */ +void EggJointNodePointer:: +apply_default_pose(EggJointPointer *source_joint, int frame) { + if (_joint != nullptr) { + LMatrix4d pose; + if (frame >= 0 && frame < source_joint->get_num_frames()) { + pose = source_joint->get_frame(frame); + } else { + pose = get_frame(0); + } + _joint->clear_default_pose(); + _joint->modify_default_pose().add_matrix4(pose); + } +} + +/** + * Returns true if there are any vertices referenced by the node this points + * to, false otherwise. For certain kinds of back pointers (e.g. table + * animation entries), this is always false. + */ +bool EggJointNodePointer:: +has_vertices() const { + if (_joint != nullptr) { + return (_joint->vref_size() != 0) || _joint->joint_has_primitives(); + } + + return false; +} + +/** + * Creates a new child of the current joint in the egg data, and returns a + * pointer to it. + */ +EggJointPointer *EggJointNodePointer:: +make_new_joint(const std::string &name) { + EggGroup *new_joint = new EggGroup(name); + new_joint->set_group_type(EggGroup::GT_joint); + _joint->add_child(new_joint); + return new EggJointNodePointer(new_joint); +} + +/** + * Applies the indicated name change to the egg file. + */ +void EggJointNodePointer:: +set_name(const std::string &name) { + _joint->set_name(name); +} diff --git a/pandatool/src/eggcharbase/eggJointNodePointer.h b/pandatool/src/eggcharbase/eggJointNodePointer.h new file mode 100644 index 00000000..8a979f11 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointNodePointer.h @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointNodePointer.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGJOINTNODEPOINTER_H +#define EGGJOINTNODEPOINTER_H + +#include "pandatoolbase.h" + +#include "eggJointPointer.h" + +#include "eggGroup.h" +#include "pointerTo.h" + +/** + * This stores a pointer back to a node. + */ +class EggJointNodePointer : public EggJointPointer { +public: + EggJointNodePointer(EggObject *object); + + virtual int get_num_frames() const; + virtual LMatrix4d get_frame(int n) const; + virtual void set_frame(int n, const LMatrix4d &mat); + + virtual void do_finish_reparent(EggJointPointer *new_parent); + virtual void move_vertices_to(EggJointPointer *new_joint); + + virtual bool do_rebuild(EggCharacterDb &db); + virtual void expose(EggGroup::DCSType dcs_type); + virtual void apply_default_pose(EggJointPointer *source_joint, int frame); + + virtual bool has_vertices() const; + + virtual EggJointPointer *make_new_joint(const std::string &name); + + virtual void set_name(const std::string &name); + +private: + PT(EggGroup) _joint; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggJointPointer::init_type(); + register_type(_type_handle, "EggJointNodePointer", + EggJointPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggJointPointer.I b/pandatool/src/eggcharbase/eggJointPointer.I new file mode 100644 index 00000000..57bd7cf3 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointPointer.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointPointer.I + * @author drose + * @date 2003-07-20 + */ diff --git a/pandatool/src/eggcharbase/eggJointPointer.cxx b/pandatool/src/eggcharbase/eggJointPointer.cxx new file mode 100644 index 00000000..6a5ed440 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointPointer.cxx @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointPointer.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggJointPointer.h" + + +TypeHandle EggJointPointer::_type_handle; + + +/** + * Appends a new frame onto the end of the data, if possible; returns true if + * not possible, or false otherwise (e.g. for a static joint). + */ +bool EggJointPointer:: +add_frame(const LMatrix4d &) { + return false; +} + +/** + * Moves the vertices assigned to this joint into the other joint (which + * should be of the same type). + */ +void EggJointPointer:: +move_vertices_to(EggJointPointer *) { +} + +/** + * Rebuilds the entire table all at once, based on the frames added by + * repeated calls to add_rebuild_frame() since the last call to + * begin_rebuild(). + * + * Until do_rebuild() is called, the animation table is not changed. + * + * The return value is true if all frames are acceptable, or false if there is + * some problem. + */ +bool EggJointPointer:: +do_rebuild(EggCharacterDb &db) { + return true; +} + +/** + * Resets the table before writing to disk so that redundant rows (e.g. i { 1 + * 1 1 1 1 1 1 1 }) are collapsed out. + */ +void EggJointPointer:: +optimize() { +} + +/** + * Flags the joint with the indicated DCS flag so that it will be loaded as a + * separate node in the player. + */ +void EggJointPointer:: +expose(EggGroup::DCSType) { +} + +/** + * Zeroes out the named components of the transform in the animation frames. + */ +void EggJointPointer:: +zero_channels(const std::string &) { +} + +/** + * Rounds the named components of the transform to the nearest multiple of + * quantum. + */ +void EggJointPointer:: +quantize_channels(const std::string &, double) { +} + +/** + * Applies the pose from the indicated frame of the indicated source joint as + * the initial pose for this joint. + */ +void EggJointPointer:: +apply_default_pose(EggJointPointer *source_joint, int frame) { +} diff --git a/pandatool/src/eggcharbase/eggJointPointer.h b/pandatool/src/eggcharbase/eggJointPointer.h new file mode 100644 index 00000000..94a8a318 --- /dev/null +++ b/pandatool/src/eggcharbase/eggJointPointer.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggJointPointer.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGJOINTPOINTER_H +#define EGGJOINTPOINTER_H + +#include "pandatoolbase.h" +#include "eggBackPointer.h" +#include "eggGroup.h" +#include "luse.h" + +class EggCharacterDb; + +/** + * This is a base class for EggJointNodePointer and EggMatrixTablePointer. It + * stores a back pointer to either a entry or an xform
data, + * and thus presents an interface that returns 1-n matrices, one for each + * frame. ( entries, for model files, appear the same as one-frame + * animations.) + */ +class EggJointPointer : public EggBackPointer { +public: + virtual int get_num_frames() const=0; + virtual LMatrix4d get_frame(int n) const=0; + virtual void set_frame(int n, const LMatrix4d &mat)=0; + virtual bool add_frame(const LMatrix4d &mat); + + virtual void do_finish_reparent(EggJointPointer *new_parent)=0; + virtual void move_vertices_to(EggJointPointer *new_joint); + + virtual bool do_rebuild(EggCharacterDb &db); + + virtual void optimize(); + virtual void expose(EggGroup::DCSType dcs_type); + virtual void zero_channels(const std::string &components); + virtual void quantize_channels(const std::string &components, double quantum); + virtual void apply_default_pose(EggJointPointer *source_joint, int frame); + + virtual EggJointPointer *make_new_joint(const std::string &name)=0; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggBackPointer::init_type(); + register_type(_type_handle, "EggJointPointer", + EggBackPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "eggJointPointer.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggMatrixTablePointer.cxx b/pandatool/src/eggcharbase/eggMatrixTablePointer.cxx new file mode 100644 index 00000000..26729648 --- /dev/null +++ b/pandatool/src/eggcharbase/eggMatrixTablePointer.cxx @@ -0,0 +1,295 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMatrixTablePointer.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggMatrixTablePointer.h" + +#include "dcast.h" +#include "eggCharacterDb.h" +#include "eggSAnimData.h" +#include "eggXfmAnimData.h" +#include "eggXfmSAnim.h" + +using std::string; + +TypeHandle EggMatrixTablePointer::_type_handle; + +/** + * + */ +EggMatrixTablePointer:: +EggMatrixTablePointer(EggObject *object) { + _table = DCAST(EggTable, object); + + if (_table != nullptr) { + // Now search for the child named "xform". This contains the actual table + // data. + EggGroupNode::iterator ci; + bool found = false; + for (ci = _table->begin(); ci != _table->end() && !found; ++ci) { + EggNode *child = (*ci); + if (child->get_name() == "xform") { + if (child->is_of_type(EggXfmSAnim::get_class_type())) { + _xform = DCAST(EggXfmSAnim, child); + _xform->normalize(); + found = true; + + } else if (child->is_of_type(EggXfmAnimData::get_class_type())) { + // Quietly replace old-style XfmAnim tables with new-style XfmSAnim + // tables. + PT(EggXfmAnimData) anim = DCAST(EggXfmAnimData, child); + _xform = new EggXfmSAnim(*anim); + _table->replace(ci, _xform.p()); + found = true; + } + } + } + } +} + +/** + * Returns the stated frame rate of this particular joint, or 0.0 if it + * doesn't state. + */ +double EggMatrixTablePointer:: +get_frame_rate() const { + if (_xform == nullptr || !_xform->has_fps()) { + return 0.0; + } else { + return _xform->get_fps(); + } +} + +/** + * Returns the number of frames of animation for this particular joint. + */ +int EggMatrixTablePointer:: +get_num_frames() const { + if (_xform == nullptr) { + return 0; + } else { + return _xform->get_num_rows(); + } +} + +/** + * Extends the table to the indicated number of frames. + */ +void EggMatrixTablePointer:: +extend_to(int num_frames) { + nassertv(_xform != nullptr); + _xform->normalize(); + int num_rows = _xform->get_num_rows(); + LMatrix4d last_mat; + if (num_rows == 0) { + last_mat = LMatrix4d::ident_mat(); + } else { + _xform->get_value(num_rows - 1, last_mat); + } + + while (num_rows < num_frames) { + _xform->add_data(last_mat); + num_rows++; + } +} + +/** + * Returns the transform matrix corresponding to this joint position in the + * nth frame. + */ +LMatrix4d EggMatrixTablePointer:: +get_frame(int n) const { + if (get_num_frames() == 1) { + // If we have exactly one frame, then we have as many frames as we want; + // just repeat the first frame. + n = 0; + + } else if (get_num_frames() == 0) { + // If we have no frames, we really have the identity matrix. + return LMatrix4d::ident_mat(); + } + + nassertr(n >= 0 && n < get_num_frames(), LMatrix4d::ident_mat()); + LMatrix4d mat; + _xform->get_value(n, mat); + return mat; +} + +/** + * Sets the transform matrix corresponding to this joint position in the nth + * frame. + */ +void EggMatrixTablePointer:: +set_frame(int n, const LMatrix4d &mat) { + nassertv(n >= 0 && n < get_num_frames()); + _xform->set_value(n, mat); +} + +/** + * Appends a new frame onto the end of the data, if possible; returns true if + * not possible, or false otherwise (e.g. for a static joint). + */ +bool EggMatrixTablePointer:: +add_frame(const LMatrix4d &mat) { + if (_xform == nullptr) { + return false; + } + + return _xform->add_data(mat); +} + +/** + * Performs the actual reparenting operation by removing the node from its old + * parent and associating it with its new parent, if any. + */ +void EggMatrixTablePointer:: +do_finish_reparent(EggJointPointer *new_parent) { + if (new_parent == nullptr) { + // No new parent; unparent the joint. + EggGroupNode *egg_parent = _table->get_parent(); + if (egg_parent != nullptr) { + egg_parent->remove_child(_table.p()); + } + + } else { + // Reparent the joint to its new parent (implicitly unparenting it from + // its previous parent). + EggMatrixTablePointer *new_node = DCAST(EggMatrixTablePointer, new_parent); + if (new_node->_table != _table->get_parent()) { + new_node->_table->add_child(_table.p()); + } + } +} + +/** + * Rebuilds the entire table all at once, based on the frames added by + * repeated calls to add_rebuild_frame() since the last call to + * begin_rebuild(). + * + * Until do_rebuild() is called, the animation table is not changed. + * + * The return value is true if all frames are acceptable, or false if there is + * some problem. + */ +bool EggMatrixTablePointer:: +do_rebuild(EggCharacterDb &db) { + LMatrix4d mat; + if (!db.get_matrix(this, EggCharacterDb::TT_rebuild_frame, 0, mat)) { + // No rebuild frame; this is OK. + return true; + } + + if (_xform == nullptr) { + return false; + } + + bool all_ok = true; + + _xform->clear_data(); + if (!_xform->add_data(mat)) { + all_ok = false; + } + + // Assume all frames will be contiguous. + int n = 1; + while (db.get_matrix(this, EggCharacterDb::TT_rebuild_frame, n, mat)) { + if (!_xform->add_data(mat)) { + all_ok = false; + } + ++n; + } + + return all_ok; +} + +/** + * Resets the table before writing to disk so that redundant rows (e.g. i { 1 + * 1 1 1 1 1 1 1 }) are collapsed out. + */ +void EggMatrixTablePointer:: +optimize() { + if (_xform != nullptr) { + _xform->optimize(); + } +} + +/** + * Zeroes out the named components of the transform in the animation frames. + */ +void EggMatrixTablePointer:: +zero_channels(const string &components) { + if (_xform == nullptr) { + return; + } + + // This is particularly easy: we only have to remove children from the + // _xform object whose name is listed in the components. + string::const_iterator si; + for (si = components.begin(); si != components.end(); ++si) { + string table_name(1, *si); + EggNode *child = _xform->find_child(table_name); + if (child != nullptr) { + _xform->remove_child(child); + } + } +} + +/** + * Rounds the named components of the transform to the nearest multiple of + * quantum. + */ +void EggMatrixTablePointer:: +quantize_channels(const string &components, double quantum) { + if (_xform == nullptr) { + return; + } + + // This is similar to the above: we quantize children of the _xform object + // whose name is listed in the components. + string::const_iterator si; + for (si = components.begin(); si != components.end(); ++si) { + string table_name(1, *si); + EggNode *child = _xform->find_child(table_name); + if (child != nullptr && + child->is_of_type(EggSAnimData::get_class_type())) { + EggSAnimData *anim = DCAST(EggSAnimData, child); + anim->quantize(quantum); + } + } +} + +/** + * Creates a new child of the current joint in the egg data, and returns a + * pointer to it. + */ +EggJointPointer *EggMatrixTablePointer:: +make_new_joint(const string &name) { + EggTable *new_table = new EggTable(name); + _table->add_child(new_table); + CoordinateSystem cs = CS_default; + if (_xform != nullptr) { + cs = _xform->get_coordinate_system(); + } + EggXfmSAnim *new_xform = new EggXfmSAnim("xform", cs); + new_table->add_child(new_xform); + new_xform->add_data(LMatrix4d::ident_mat()); + + return new EggMatrixTablePointer(new_table); +} + +/** + * Applies the indicated name change to the egg file. + */ +void EggMatrixTablePointer:: +set_name(const string &name) { + _table->set_name(name); +} diff --git a/pandatool/src/eggcharbase/eggMatrixTablePointer.h b/pandatool/src/eggcharbase/eggMatrixTablePointer.h new file mode 100644 index 00000000..cbdeeecc --- /dev/null +++ b/pandatool/src/eggcharbase/eggMatrixTablePointer.h @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMatrixTablePointer.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGMATRIXTABLEPOINTER_H +#define EGGMATRIXTABLEPOINTER_H + +#include "pandatoolbase.h" + +#include "eggJointPointer.h" + +#include "eggTable.h" +#include "eggXfmSAnim.h" +#include "pointerTo.h" + +/** + * This stores a pointer back to an EggXfmSAnim table (i.e. an + * entry in an egg file), corresponding to the animation data from a single + * bundle for this joint. + */ +class EggMatrixTablePointer : public EggJointPointer { +public: + EggMatrixTablePointer(EggObject *object); + + virtual double get_frame_rate() const; + virtual int get_num_frames() const; + virtual void extend_to(int num_frames); + virtual LMatrix4d get_frame(int n) const; + virtual void set_frame(int n, const LMatrix4d &mat); + virtual bool add_frame(const LMatrix4d &mat); + + virtual void do_finish_reparent(EggJointPointer *new_parent); + + virtual bool do_rebuild(EggCharacterDb &db); + + virtual void optimize(); + virtual void zero_channels(const std::string &components); + virtual void quantize_channels(const std::string &components, double quantum); + + virtual EggJointPointer *make_new_joint(const std::string &name); + + virtual void set_name(const std::string &name); + +private: + PT(EggTable) _table; + PT(EggXfmSAnim) _xform; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggJointPointer::init_type(); + register_type(_type_handle, "EggMatrixTablePointer", + EggJointPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggScalarTablePointer.cxx b/pandatool/src/eggcharbase/eggScalarTablePointer.cxx new file mode 100644 index 00000000..450bb548 --- /dev/null +++ b/pandatool/src/eggcharbase/eggScalarTablePointer.cxx @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggScalarTablePointer.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "eggScalarTablePointer.h" + +#include "dcast.h" + +TypeHandle EggScalarTablePointer::_type_handle; + +/** + * + */ +EggScalarTablePointer:: +EggScalarTablePointer(EggObject *object) { + _data = DCAST(EggSAnimData, object); +} + +/** + * Returns the stated frame rate of this particular joint, or 0.0 if it + * doesn't state. + */ +double EggScalarTablePointer:: +get_frame_rate() const { + if (_data == nullptr || !_data->has_fps()) { + return 0.0; + } else { + return _data->get_fps(); + } +} + +/** + * Returns the number of frames of animation for this particular slider. + */ +int EggScalarTablePointer:: +get_num_frames() const { + if (_data == nullptr) { + return 0; + } else { + return _data->get_num_rows(); + } +} + +/** + * Extends the table to the indicated number of frames. + */ +void EggScalarTablePointer:: +extend_to(int num_frames) { + nassertv(_data != nullptr); + int num_rows = _data->get_num_rows(); + double last_value; + if (num_rows == 0) { + last_value = 0.0; + } else { + last_value = _data->get_value(num_rows - 1); + } + + while (num_rows < num_frames) { + _data->add_data(last_value); + num_rows++; + } +} + +/** + * Returns the value corresponding to this slider position in the nth frame. + */ +double EggScalarTablePointer:: +get_frame(int n) const { + if (get_num_frames() == 1) { + // If we have exactly one frame, then we have as many frames as we want; + // just repeat the first frame. + n = 0; + } + + nassertr(n >= 0 && n < get_num_frames(), 0.0); + return _data->get_value(n); +} + +/** + * Applies the indicated name change to the egg file. + */ +void EggScalarTablePointer:: +set_name(const std::string &name) { + // Actually, let's not rename the slider table (yet), because we haven't + // written the code to rename all of the morph targets. + + // _data->set_name(name); +} diff --git a/pandatool/src/eggcharbase/eggScalarTablePointer.h b/pandatool/src/eggcharbase/eggScalarTablePointer.h new file mode 100644 index 00000000..c0a74879 --- /dev/null +++ b/pandatool/src/eggcharbase/eggScalarTablePointer.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggScalarTablePointer.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef EGGSCALARTABLEPOINTER_H +#define EGGSCALARTABLEPOINTER_H + +#include "pandatoolbase.h" + +#include "eggSliderPointer.h" + +#include "eggSAnimData.h" +#include "pointerTo.h" + +/** + * This stores a pointer back to an EggSAnimData table (i.e. an + * entry in an egg file), corresponding to the animation data from a single + * bundle for this slider. + */ +class EggScalarTablePointer : public EggSliderPointer { +public: + EggScalarTablePointer(EggObject *object); + + virtual double get_frame_rate() const; + virtual int get_num_frames() const; + virtual void extend_to(int num_frames); + virtual double get_frame(int n) const; + + virtual void set_name(const std::string &name); + +private: + PT(EggSAnimData) _data; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggSliderPointer::init_type(); + register_type(_type_handle, "EggScalarTablePointer", + EggSliderPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggSliderData.I b/pandatool/src/eggcharbase/eggSliderData.I new file mode 100644 index 00000000..d925c04a --- /dev/null +++ b/pandatool/src/eggcharbase/eggSliderData.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSliderData.I + * @author drose + * @date 2001-02-26 + */ diff --git a/pandatool/src/eggcharbase/eggSliderData.cxx b/pandatool/src/eggcharbase/eggSliderData.cxx new file mode 100644 index 00000000..91d04136 --- /dev/null +++ b/pandatool/src/eggcharbase/eggSliderData.cxx @@ -0,0 +1,102 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSliderData.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggSliderData.h" +#include "eggVertexPointer.h" +#include "eggScalarTablePointer.h" +#include "eggSliderPointer.h" +#include "dcast.h" +#include "eggPrimitive.h" +#include "eggVertex.h" +#include "eggSAnimData.h" +#include "indent.h" + +TypeHandle EggSliderData::_type_handle; + +/** + * + */ +EggSliderData:: +EggSliderData(EggCharacterCollection *collection, + EggCharacterData *char_data) : + EggComponentData(collection, char_data) +{ +} + +/** + * Returns the value corresponding to this slider position in the nth frame in + * the indicated model. + */ +double EggSliderData:: +get_frame(int model_index, int n) const { + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + return 0.0; + } + + EggSliderPointer *slider; + DCAST_INTO_R(slider, back, 0.0); + + return slider->get_frame(n); +} + +/** + * Adds the indicated vertex, primitive, or morph table to the data. + */ +void EggSliderData:: +add_back_pointer(int model_index, EggObject *egg_object) { + if (egg_object->is_of_type(EggPrimitive::get_class_type())) { + // A primitive! + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + back = new EggVertexPointer(egg_object); + set_model(model_index, back); + } + + } else if (egg_object->is_of_type(EggVertex::get_class_type())) { + // A vertex! + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + back = new EggVertexPointer(egg_object); + set_model(model_index, back); + } + + } else if (egg_object->is_of_type(EggSAnimData::get_class_type())) { + // A slider animation table! Woo hoo! + EggBackPointer *back = get_model(model_index); + if (back == nullptr) { + back = new EggScalarTablePointer(egg_object); + set_model(model_index, back); + } + + } else { + nout << "Invalid object added to slider for back pointer.\n"; + } +} + +/** + * + */ +void EggSliderData:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << "Slider " << get_name() + << " (models:"; + int num_models = get_num_models(); + for (int model_index = 0; model_index < num_models; model_index++) { + if (has_model(model_index)) { + out << " " << model_index; + } + } + out << ")\n"; +} diff --git a/pandatool/src/eggcharbase/eggSliderData.h b/pandatool/src/eggcharbase/eggSliderData.h new file mode 100644 index 00000000..dd0e4c34 --- /dev/null +++ b/pandatool/src/eggcharbase/eggSliderData.h @@ -0,0 +1,59 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSliderData.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGSLIDERDATA_H +#define EGGSLIDERDATA_H + +#include "pandatoolbase.h" + +#include "eggComponentData.h" + + +/** + * This corresponds to a single morph slider control. It contains back + * pointers to all the vertices and primitives that reference this slider + * across all models, as well as all the tables in which it appears in all + * animation files. + */ +class EggSliderData : public EggComponentData { +public: + EggSliderData(EggCharacterCollection *collection, + EggCharacterData *char_data); + + double get_frame(int model_index, int n) const; + + virtual void add_back_pointer(int model_index, EggObject *egg_object); + virtual void write(std::ostream &out, int indent_level = 0) const; + + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggComponentData::init_type(); + register_type(_type_handle, "EggSliderData", + EggComponentData::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "eggSliderData.I" + +#endif diff --git a/pandatool/src/eggcharbase/eggSliderPointer.cxx b/pandatool/src/eggcharbase/eggSliderPointer.cxx new file mode 100644 index 00000000..bad2934a --- /dev/null +++ b/pandatool/src/eggcharbase/eggSliderPointer.cxx @@ -0,0 +1,16 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSliderPointer.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "eggSliderPointer.h" + +TypeHandle EggSliderPointer::_type_handle; diff --git a/pandatool/src/eggcharbase/eggSliderPointer.h b/pandatool/src/eggcharbase/eggSliderPointer.h new file mode 100644 index 00000000..19129f45 --- /dev/null +++ b/pandatool/src/eggcharbase/eggSliderPointer.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggSliderPointer.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef EGGSLIDERPOINTER_H +#define EGGSLIDERPOINTER_H + +#include "pandatoolbase.h" + +#include "eggBackPointer.h" + +#include "luse.h" + +/** + * This is a base class for EggVertexPointer and EggScalarTablePointer. + */ +class EggSliderPointer : public EggBackPointer { +public: + virtual int get_num_frames() const=0; + virtual double get_frame(int n) const=0; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggBackPointer::init_type(); + register_type(_type_handle, "EggSliderPointer", + EggBackPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/eggVertexPointer.cxx b/pandatool/src/eggcharbase/eggVertexPointer.cxx new file mode 100644 index 00000000..09af9d77 --- /dev/null +++ b/pandatool/src/eggcharbase/eggVertexPointer.cxx @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggVertexPointer.cxx + * @author drose + * @date 2001-02-26 + */ + +#include "eggVertexPointer.h" + + +TypeHandle EggVertexPointer::_type_handle; + +/** + * + */ +EggVertexPointer:: +EggVertexPointer(EggObject *egg_object) { +} + +/** + * Returns the number of frames of animation for this particular slider. + */ +int EggVertexPointer:: +get_num_frames() const { + return 0; +} + +/** + * Returns the value corresponding to this slider position in the nth frame. + */ +double EggVertexPointer:: +get_frame(int n) const { + nassertr(false, 0.0); + return 0.0; +} + +/** + * Returns true if there are any vertices referenced by the node this points + * to, false otherwise. For certain kinds of back pointers (e.g. table + * animation entries), this is always false. + */ +bool EggVertexPointer:: +has_vertices() const { + return true; +} diff --git a/pandatool/src/eggcharbase/eggVertexPointer.h b/pandatool/src/eggcharbase/eggVertexPointer.h new file mode 100644 index 00000000..8403edab --- /dev/null +++ b/pandatool/src/eggcharbase/eggVertexPointer.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggVertexPointer.h + * @author drose + * @date 2001-02-26 + */ + +#ifndef EGGVERTEXPOINTER_H +#define EGGVERTEXPOINTER_H + +#include "pandatoolbase.h" + +#include "eggSliderPointer.h" + +#include "eggGroup.h" +#include "pointerTo.h" + +/** + * This stores a pointer back to a , or to a particular pritimive like + * a , representing a morph offset. + */ +class EggVertexPointer : public EggSliderPointer { +public: + EggVertexPointer(EggObject *egg_object); + + virtual int get_num_frames() const; + virtual double get_frame(int n) const; + + virtual bool has_vertices() const; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + EggSliderPointer::init_type(); + register_type(_type_handle, "EggVertexPointer", + EggSliderPointer::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/eggcharbase/p3eggcharbase_composite1.cxx b/pandatool/src/eggcharbase/p3eggcharbase_composite1.cxx new file mode 100644 index 00000000..5aa73594 --- /dev/null +++ b/pandatool/src/eggcharbase/p3eggcharbase_composite1.cxx @@ -0,0 +1,17 @@ + +#include "config_eggcharbase.cxx" +#include "eggBackPointer.cxx" +#include "eggCharacterCollection.cxx" +#include "eggCharacterData.cxx" +#include "eggCharacterDb.cxx" +#include "eggCharacterFilter.cxx" +#include "eggComponentData.cxx" +#include "eggJointData.cxx" +#include "eggJointPointer.cxx" +#include "eggJointNodePointer.cxx" +#include "eggMatrixTablePointer.cxx" +#include "eggScalarTablePointer.cxx" +#include "eggSliderData.cxx" +#include "eggSliderPointer.cxx" +#include "eggVertexPointer.cxx" + diff --git a/pandatool/src/eggprogs/CMakeLists.txt b/pandatool/src/eggprogs/CMakeLists.txt new file mode 100644 index 00000000..e845c57d --- /dev/null +++ b/pandatool/src/eggprogs/CMakeLists.txt @@ -0,0 +1,43 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG) + return() +endif() + +add_executable(egg-crop eggCrop.cxx eggCrop.h) +target_link_libraries(egg-crop p3eggbase) +install(TARGETS egg-crop EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-list-textures eggListTextures.cxx eggListTextures.h) +target_link_libraries(egg-list-textures p3eggbase) +install(TARGETS egg-list-textures EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-make-tube eggMakeTube.cxx eggMakeTube.h) +target_link_libraries(egg-make-tube p3eggbase) +install(TARGETS egg-make-tube EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-rename eggRename.cxx eggRename.h) +target_link_libraries(egg-rename p3eggbase) +install(TARGETS egg-rename EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-retarget-anim eggRetargetAnim.cxx eggRetargetAnim.h) +target_link_libraries(egg-retarget-anim p3eggcharbase) +install(TARGETS egg-retarget-anim EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-texture-cards eggTextureCards.cxx eggTextureCards.h) +target_link_libraries(egg-texture-cards p3eggbase) +install(TARGETS egg-texture-cards EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg2c eggToC.cxx eggToC.h) +target_link_libraries(egg2c p3eggbase) +install(TARGETS egg2c EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-topstrip eggTopstrip.cxx eggTopstrip.h) +target_link_libraries(egg-topstrip p3eggcharbase) +install(TARGETS egg-topstrip EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(egg-trans eggTrans.cxx eggTrans.h) +target_link_libraries(egg-trans p3eggbase) +install(TARGETS egg-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/eggprogs/eggCrop.cxx b/pandatool/src/eggprogs/eggCrop.cxx new file mode 100644 index 00000000..38edb932 --- /dev/null +++ b/pandatool/src/eggprogs/eggCrop.cxx @@ -0,0 +1,124 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCrop.cxx + * @author drose + * @date 2002-06-10 + */ + +#include "eggCrop.h" + +#include "eggGroupNode.h" +#include "eggPrimitive.h" +#include "eggVertex.h" +#include "dcast.h" + +/** + * + */ +EggCrop:: +EggCrop() { + set_program_brief("crop geometry in an .egg file"); + set_program_description + ("egg-crop strips out all parts of an egg file that fall outside of an " + "arbitrary bounding volume, specified with a minimum and maximum point " + "in world coordinates."); + + add_option + ("min", "x,y,z", 0, + "Specify the minimum point.", + &EggCrop::dispatch_double_triple, &_got_min, &_min[0]); + + add_option + ("max", "x,y,z", 0, + "Specify the maximum point.", + &EggCrop::dispatch_double_triple, &_got_max, &_max[0]); +} + +/** + * This is called after the command line has been completely processed, and it + * gives the program a chance to do some last-minute processing and validation + * of the options and arguments. It should return true if everything is fine, + * false if there is an error. + */ +bool EggCrop:: +post_command_line() { + if (!_got_min || !_got_max) { + nout << "You must specify both a minimum and a maximum bounds.\n"; + return false; + } + + return true; +} + +/** + * + */ +void EggCrop:: +run() { + int num_removed = strip_prims(_data); + nout << "Removed " << num_removed << " primitives.\n"; + + _data->remove_unused_vertices(true); + write_egg_file(); +} + + +/** + * Recursively walks the scene graph, looking for primitives that exceed the + * specified bounding volume, and removes them. Returns the number of + * primitives removed. + */ +int EggCrop:: +strip_prims(EggGroupNode *group) { + int num_removed = 0; + + EggGroupNode::iterator ci; + ci = group->begin(); + while (ci != group->end()) { + EggNode *child = (*ci); + bool all_in = true; + + if (child->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *prim = DCAST(EggPrimitive, child); + EggPrimitive::iterator vi; + for (vi = prim->begin(); vi != prim->end() && all_in; ++vi) { + EggVertex *vert = (*vi); + LPoint3d pos = vert->get_pos3(); + + all_in = (pos[0] >= _min[0] && pos[0] <= _max[0] && + pos[1] >= _min[1] && pos[1] <= _max[1] && + pos[2] >= _min[2] && pos[2] <= _max[2]); + + } + } + + if (!all_in) { + // Reject this primitive. + ci = group->erase(ci); + num_removed++; + } else { + // Keep this primitive. + if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group_child = DCAST(EggGroupNode, child); + num_removed += strip_prims(group_child); + } + ++ci; + } + } + + return num_removed; +} + + +int main(int argc, char *argv[]) { + EggCrop prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggCrop.h b/pandatool/src/eggprogs/eggCrop.h new file mode 100644 index 00000000..df3203c8 --- /dev/null +++ b/pandatool/src/eggprogs/eggCrop.h @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggCrop.h + * @author drose + * @date 2002-06-10 + */ + +#ifndef EGGCROP_H +#define EGGCROP_H + +#include "pandatoolbase.h" + +#include "eggFilter.h" + +class EggGroupNode; + +/** + * A program to read an egg file and write an equivalent egg file, possibly + * performing some minor operations along the way. + */ +class EggCrop : public EggFilter { +public: + EggCrop(); + + virtual bool post_command_line(); + void run(); + +private: + int strip_prims(EggGroupNode *group); + + bool _got_min, _got_max; + LVecBase3d _min, _max; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggListTextures.cxx b/pandatool/src/eggprogs/eggListTextures.cxx new file mode 100644 index 00000000..fd44c73a --- /dev/null +++ b/pandatool/src/eggprogs/eggListTextures.cxx @@ -0,0 +1,65 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggListTextures.cxx + * @author drose + * @date 2005-05-23 + */ + +#include "eggListTextures.h" +#include "eggTextureCollection.h" +#include "pnmImageHeader.h" + +/** + * + */ +EggListTextures:: +EggListTextures() { + set_program_brief("list textures referenced by an .egg file"); + set_program_description + ("egg-list-textures reads an egg file and writes a list of the " + "textures it references. It is particularly useful for building " + "up the textures.txa file used for egg-palettize, since the output " + "format is crafted to be compatible with that file's input format."); +} + +/** + * + */ +void EggListTextures:: +run() { + if (!do_reader_options()) { + exit(1); + } + + EggTextureCollection tc; + tc.find_used_textures(_data); + EggTextureCollection::TextureReplacement treplace; + tc.collapse_equivalent_textures(EggTexture::E_complete_filename, treplace); + tc.sort_by_basename(); + + EggTextureCollection::iterator ti; + for (ti = tc.begin(); ti != tc.end(); ++ti) { + Filename fullpath = (*ti)->get_fullpath(); + PNMImageHeader header; + if (header.read_header(fullpath)) { + std::cout << fullpath.get_basename() << " : " + << header.get_x_size() << " " << header.get_y_size() << "\n"; + } else { + std::cout << fullpath.get_basename() << " : unknown\n"; + } + } +} + + +int main(int argc, char *argv[]) { + EggListTextures prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggListTextures.h b/pandatool/src/eggprogs/eggListTextures.h new file mode 100644 index 00000000..5460b49e --- /dev/null +++ b/pandatool/src/eggprogs/eggListTextures.h @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggListTextures.h + * @author drose + * @date 2005-05-23 + */ + +#ifndef EGGLISTTEXTURES_H +#define EGGLISTTEXTURES_H + +#include "pandatoolbase.h" + +#include "eggReader.h" + +/** + * Reads an egg file and outputs the list of textures it uses. + */ +class EggListTextures : public EggReader { +public: + EggListTextures(); + + void run(); +}; + +#endif diff --git a/pandatool/src/eggprogs/eggMakeTube.cxx b/pandatool/src/eggprogs/eggMakeTube.cxx new file mode 100644 index 00000000..85ec8508 --- /dev/null +++ b/pandatool/src/eggprogs/eggMakeTube.cxx @@ -0,0 +1,270 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeTube.cxx + * @author drose + * @date 2003-10-01 + */ + +#include "eggMakeTube.h" +#include "eggGroup.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggPolygon.h" +#include "pointerTo.h" +#include "look_at.h" + +/** + * + */ +EggMakeTube:: +EggMakeTube() { + + set_program_brief("generate a tube or sphere from geometry in an .egg file"); + set_program_description + ("egg-make-tube generates an egg file representing a \"tube\" model, " + "a cylinder capped on both ends by hemispheres. This is similar " + "in shape to the CollisionCapsule object within Panda.\n\n" + "This program can also generate spheres if you omit -b; in this " + "case, you are generating a degenerate tube of length 0."); + + add_option + ("a", "x,y,z", 0, + "Specify the first endpoint of the tube.", + &EggWriter::dispatch_double_triple, nullptr, _point_a); + + add_option + ("b", "x,y,z", 0, + "Specify the second endpoint of the tube.", + &EggWriter::dispatch_double_triple, &_got_point_b, _point_b); + + add_option + ("r", "radius", 0, + "Specify the radius of the tube. The tube will extend beyond " + "the endpoints in each direction by the amount of radius.", + &EggWriter::dispatch_double, nullptr, &_radius); + + add_option + ("slices", "count", 0, + "Specify the number of slices appearing radially around the tube.", + &EggWriter::dispatch_int, nullptr, &_num_slices); + + add_option + ("crings", "count", 0, + "Specify the number of rings appearing in each endcap of the tube.", + &EggWriter::dispatch_int, nullptr, &_num_crings); + + add_option + ("trings", "count", 0, + "Specify the number of rings appearing in the cylindrical body " + "of the tube.", + &EggWriter::dispatch_int, nullptr, &_num_trings); + + _point_a[0] = 0.0; + _point_a[1] = 0.0; + _point_a[2] = 0.0; + + _point_b[0] = 0.0; + _point_b[1] = 0.0; + _point_b[2] = 0.0; + + _radius = 1.0; + + _num_slices = 8; + _num_crings = 4; + _num_trings = 1; +} + +/** + * + */ +void EggMakeTube:: +run() { + if (!_got_point_b) { + _point_b[0] = _point_a[0]; + _point_b[1] = _point_a[1]; + _point_b[2] = _point_a[2]; + } + + // We will generate the vertices in the canonical space (along the y axis), + // then transform it to the desired point. + LVector3d direction(_point_b[0] - _point_a[0], + _point_b[1] - _point_a[1], + _point_b[2] - _point_a[2]); + _length = direction.length(); + + // First, create an enclosing group and a vertex pool. + _group = new EggGroup("tube"); + _data->add_child(_group); + + _vpool = new EggVertexPool("tube"); + _group->add_child(_vpool); + + // Generate the first endcap. + int ri, si; + EggVertex *vtx_1; + EggVertex *vtx_2; + + for (ri = 0; ri < _num_crings; ri++) { + vtx_1 = nullptr; + vtx_2 = nullptr; + for (si = 0; si <= _num_slices; si++) { + EggVertex *vtx_3 = calc_sphere1_vertex(ri, si); + EggVertex *vtx_4 = calc_sphere1_vertex(ri + 1, si); + add_polygon(vtx_1, vtx_2, vtx_4, vtx_3); + vtx_1 = vtx_3; + vtx_2 = vtx_4; + } + } + + // Now the cylinder sides. + if (_length != 0.0) { + for (ri = 0; ri < _num_trings; ri++) { + vtx_1 = nullptr; + vtx_2 = nullptr; + for (si = 0; si <= _num_slices; si++) { + EggVertex *vtx_3 = calc_tube_vertex(ri, si); + EggVertex *vtx_4 = calc_tube_vertex(ri + 1, si); + add_polygon(vtx_1, vtx_2, vtx_4, vtx_3); + vtx_1 = vtx_3; + vtx_2 = vtx_4; + } + } + } + + // And the second endcap. + for (ri = _num_crings - 1; ri >= 0; ri--) { + vtx_1 = nullptr; + vtx_2 = nullptr; + for (si = 0; si <= _num_slices; si++) { + EggVertex *vtx_3 = calc_sphere2_vertex(ri + 1, si); + EggVertex *vtx_4 = calc_sphere2_vertex(ri, si); + add_polygon(vtx_1, vtx_2, vtx_4, vtx_3); + vtx_1 = vtx_3; + vtx_2 = vtx_4; + } + } + + // Now transform the vertices out of the canonical position. + LMatrix4d mat; + look_at(mat, direction, LVector3d(0.0, 0.0, 1.0), CS_zup_right); + mat.set_row(3, LPoint3d(_point_a[0], _point_a[1], _point_a[2])); + _group->transform(mat); + + write_egg_file(); +} + +/** + * Calculates a particular vertex on the surface of the first endcap + * hemisphere. + */ +EggVertex *EggMakeTube:: +calc_sphere1_vertex(int ri, int si) { + double r = (double)ri / (double)_num_crings; + double s = (double)si / (double)_num_slices; + + // Find the point on the rim, based on the slice. + double theta = s * 2.0 * MathNumbers::pi; + double x_rim = cos(theta); + double z_rim = sin(theta); + + // Now pull that point in towards the pole, based on the ring. + double phi = r * 0.5 * MathNumbers::pi; + double to_pole = sin(phi); + + double x = _radius * x_rim * to_pole; + double y = -_radius * cos(phi); + double z = _radius * z_rim * to_pole; + + EggVertex vert; + vert.set_pos(LPoint3d(x, y, z)); + + return _vpool->create_unique_vertex(vert); +} + +/** + * Calculates a vertex on the side of the cylindrical body of the tube. + */ +EggVertex *EggMakeTube:: +calc_tube_vertex(int ri, int si) { + double r = (double)ri / (double)_num_trings; + double s = (double)si / (double)_num_slices; + + // Find the point on the rim, based on the slice. + double theta = s * 2.0 * MathNumbers::pi; + double x_rim = cos(theta); + double z_rim = sin(theta); + + double x = _radius * x_rim; + double y = _length * r; + double z = _radius * z_rim; + + EggVertex vert; + vert.set_pos(LPoint3d(x, y, z)); + + return _vpool->create_unique_vertex(vert); +} + +/** + * Calculates a particular vertex on the surface of the second endcap + * hemisphere. + */ +EggVertex *EggMakeTube:: +calc_sphere2_vertex(int ri, int si) { + double r = (double)ri / (double)_num_crings; + double s = (double)si / (double)_num_slices; + + // Find the point on the rim, based on the slice. + double theta = s * 2.0 * MathNumbers::pi; + double x_rim = cos(theta); + double z_rim = sin(theta); + + // Now pull that point in towards the pole, based on the ring. + double phi = r * 0.5 * MathNumbers::pi; + double to_pole = sin(phi); + + double x = _radius * x_rim * to_pole; + double y = _length + _radius * cos(phi); + double z = _radius * z_rim * to_pole; + + EggVertex vert; + vert.set_pos(LPoint3d(x, y, z)); + + return _vpool->create_unique_vertex(vert); +} + +/** + * Adds the polygon defined by the indicated four vertices to the group. If + * the first vertex is NULL, does nothing. + */ +void EggMakeTube:: +add_polygon(EggVertex *a, EggVertex *b, EggVertex *c, EggVertex *d) { + if (a == nullptr) { + return; + } + + PT(EggPolygon) poly = new EggPolygon; + poly->add_vertex(a); + if (a != b) { + poly->add_vertex(b); + } + poly->add_vertex(c); + if (c != d) { + poly->add_vertex(d); + } + + _group->add_child(poly.p()); +} + + +int main(int argc, char *argv[]) { + EggMakeTube prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggMakeTube.h b/pandatool/src/eggprogs/eggMakeTube.h new file mode 100644 index 00000000..3031b1c7 --- /dev/null +++ b/pandatool/src/eggprogs/eggMakeTube.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggMakeTube.h + * @author drose + * @date 2003-10-01 + */ + +#ifndef EGGMAKETUBE_H +#define EGGMAKETUBE_H + +#include "pandatoolbase.h" + +#include "eggMakeSomething.h" + +class EggGroup; +class EggVertexPool; +class EggVertex; + +/** + * A program to generate an egg file representing a tube model, similar in + * shape to a CollisionCapsule. + */ +class EggMakeTube : public EggMakeSomething { +public: + EggMakeTube(); + + void run(); + +private: + EggVertex *calc_sphere1_vertex(int ri, int si); + EggVertex *calc_sphere2_vertex(int ri, int si); + EggVertex *calc_tube_vertex(int ri, int si); + void add_polygon(EggVertex *a, EggVertex *b, EggVertex *c, EggVertex *d); + +private: + double _point_a[3]; + double _point_b[3]; + bool _got_point_b; + double _radius; + int _num_slices; + int _num_crings; + int _num_trings; + + double _length; + EggGroup *_group; + EggVertexPool *_vpool; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggRename.cxx b/pandatool/src/eggprogs/eggRename.cxx new file mode 100644 index 00000000..6b2c9869 --- /dev/null +++ b/pandatool/src/eggprogs/eggRename.cxx @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggRename.cxx + * @author masad + * @date 2005-04-22 + */ + +#include "eggRename.h" + +/** + * + */ +EggRename:: +EggRename() { + set_program_brief("rename nodes in .egg files"); + set_program_description + ("egg-rename reads one or more egg files and writes back with modified" + "node names. ie. suppressing prefix from all the nodes' names. "); + + add_option + ("strip_prefix", "name", 0, + "strips out the prefix that is put on all nodes, by maya ext. ref", + &EggRename::dispatch_vector_string, nullptr, &_strip_prefix); +} + +/** + * + */ +void EggRename:: +run() { + if (!_strip_prefix.empty()) { + nout << "Stripping prefix from nodes.\n"; + int num_renamed = 0; + //int num_egg_files = 0; + Eggs::iterator ei; + for (ei = _eggs.begin(); ei != _eggs.end(); ++ei) { + num_renamed += (*ei)->rename_nodes(_strip_prefix, true); + //++num_egg_files; + } + nout << " (" << num_renamed << " renamed.)\n"; + } + + write_eggs(); +} + + +int main(int argc, char *argv[]) { + EggRename prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggRename.h b/pandatool/src/eggprogs/eggRename.h new file mode 100644 index 00000000..51bd5344 --- /dev/null +++ b/pandatool/src/eggprogs/eggRename.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggRename.h + * @author masad + * @date 2005-04-22 + */ + +#ifndef EGGRENAME_H +#define EGGRENAME_H + +#include "pandatoolbase.h" + +#include "eggMultiFilter.h" + +/** + * A program to read an egg file and write an equivalent egg file, with + * stripping prefix for now, but more along the way. + */ +class EggRename : public EggMultiFilter { +public: + EggRename(); + + void run(); + + vector_string _strip_prefix; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggRetargetAnim.cxx b/pandatool/src/eggprogs/eggRetargetAnim.cxx new file mode 100644 index 00000000..ba55d19b --- /dev/null +++ b/pandatool/src/eggprogs/eggRetargetAnim.cxx @@ -0,0 +1,192 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggRetargetAnim.cxx + * @author drose + * @date 2005-05-05 + */ + +#include "eggRetargetAnim.h" + +#include "dcast.h" +#include "eggJointData.h" +#include "eggCharacterCollection.h" +#include "eggCharacterData.h" +#include "eggCharacterDb.h" +#include "eggJointPointer.h" +#include "eggTable.h" +#include "compose_matrix.h" + +/** + * + */ +EggRetargetAnim:: +EggRetargetAnim() { + add_path_replace_options(); + add_path_store_options(); + + set_program_brief("remove transformations from animation data in .egg files"); + set_program_description + ("egg-retarget-anim reads a character model and its associated animation " + "files, and removes the translations and scales from the animation " + "files, replacing them with the translations and scales from the " + "rest position of the character model.\n\n" + + "This allows an animation that was generated for a model with one " + "skeleton to be played successfully on a model with a different " + "skeleton, provided that both skeletons have the same hierarchy and " + "differ only in scales and/or translations of the various joints, " + "and that scales and translations are not part of the per-frame " + "animations."); + + add_option + ("r", "file.egg", 0, + "Read the reference model from the indicated egg file. All of the " + "animations will be retargeted to match the indicated file.", + &EggRetargetAnim::dispatch_filename, nullptr, &_reference_filename); + + add_option + ("keep", "joint[,joint...]", 0, + "Preserve the full animation on the named joint(s). This is especially " + "appropriate for the root joint.", + &EggRetargetAnim::dispatch_vector_string_comma, nullptr, &_keep_joints); +} + +/** + * + */ +void EggRetargetAnim:: +run() { + nassertv(_collection != nullptr); + nassertv(_collection->get_num_eggs() > 0); + + if (_reference_filename.empty()) { + nout << "No reference filename specified.\n"; + exit(1); + } + + int num_characters = _collection->get_num_characters(); + if (num_characters != 1) { + nout << "All animations must have the same character name.\n"; + exit(1); + } + + // Read in the extra egg file that we use for extracting the references out. + PT(EggData) reference_egg = read_egg(_reference_filename); + if (reference_egg == nullptr) { + nout << "Cannot read " << _reference_filename << "\n"; + exit(1); + } + + // First, we add it to a separate EggCharacterCollection, so we can figure + // out its name. + EggCharacterCollection col; + if (col.add_egg(reference_egg) < 0) { + nout << _reference_filename + << " does not contain a character model or animation reference.\n"; + exit(1); + } + + if (col.get_num_characters() != 1) { + nout << "Reference model must contain only one character.\n"; + exit(1); + } + + std::string ref_name = col.get_character(0)->get_name(); + + // Now rename all of the animations to the same name as the reference model, + // and add the reference animation in to the same collection to match it up + // joint-for-joint. + _collection->rename_char(0, ref_name); + int reference_egg_index = _collection->add_egg(reference_egg); + nassertv(reference_egg_index > 0); + nassertv(_collection->get_num_characters() == 1); + + int reference_model = _collection->get_first_model_index(reference_egg_index); + EggCharacterData *char_data = _collection->get_character(0); + nout << "Processing " << char_data->get_name() << "\n"; + + typedef pset Names; + Names keep_names; + + vector_string::const_iterator si; + for (si = _keep_joints.begin(); si != _keep_joints.end(); ++si) { + keep_names.insert(*si); + } + + EggCharacterDb db; + EggJointData *root_joint = char_data->get_root_joint(); + retarget_anim(char_data, root_joint, reference_model, keep_names, db); + root_joint->do_rebuild_all(db); + + write_eggs(); +} + +/** + * Recursively replaces the scale and translate information on all of the + * joints in the char_data hierarchy wiht this from reference_char. + */ +void EggRetargetAnim:: +retarget_anim(EggCharacterData *char_data, EggJointData *joint_data, + int reference_model, const pset &keep_names, + EggCharacterDb &db) { + if (keep_names.find(joint_data->get_name()) != keep_names.end()) { + // Don't retarget this joint; keep the translation and scale and whatever. + + } else { + // Retarget this joint. + int num_models = joint_data->get_num_models(); + for (int i = 0; i < num_models; i++) { + if (joint_data->has_model(i)) { + int num_frames = char_data->get_num_frames(i); + + EggBackPointer *back = joint_data->get_model(i); + nassertv(back != nullptr); + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + + LMatrix4d ref = joint_data->get_frame(reference_model, 0); + LVecBase3d ref_scale, ref_shear, ref_hpr, ref_translate; + if (!decompose_matrix(ref, ref_scale, ref_shear, ref_hpr, ref_translate)) { + nout << "Could not decompose rest frame for " + << joint_data->get_name() << "\n"; + } else { + int f; + for (f = 0; f < num_frames; f++) { + LMatrix4d mat = joint_data->get_frame(i, f); + + LVecBase3d scale, shear, hpr, translate; + if (decompose_matrix(mat, scale, shear, hpr, translate)) { + compose_matrix(mat, ref_scale, ref_shear, hpr, ref_translate); + } else { + nout << "Could not decompose matrix for " << joint_data->get_name() + << "\n"; + } + + db.set_matrix(joint, EggCharacterDb::TT_rebuild_frame, + f, mat); + } + } + } + } + } + + int num_children = joint_data->get_num_children(); + for (int i = 0; i < num_children; i++) { + EggJointData *next_joint_data = joint_data->get_child(i); + retarget_anim(char_data, next_joint_data, reference_model, keep_names, db); + } +} + + +int main(int argc, char *argv[]) { + EggRetargetAnim prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggRetargetAnim.h b/pandatool/src/eggprogs/eggRetargetAnim.h new file mode 100644 index 00000000..455942ae --- /dev/null +++ b/pandatool/src/eggprogs/eggRetargetAnim.h @@ -0,0 +1,47 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggRetargetAnim.h + * @author drose + * @date 2005-05-05 + */ + +#ifndef EGGRETARGETANIM_H +#define EGGRETARGETANIM_H + +#include "pandatoolbase.h" + +#include "eggCharacterFilter.h" +#include "luse.h" +#include "pvector.h" +#include "pset.h" + +class EggCharacterData; +class EggJointData; +class EggCharacterDb; + +/** + * Retargets one or more animation files from one particular skeleton to a + * similar, but differently scaled skeleton by preserving the rotation + * information but discarding translation and/or scale. + */ +class EggRetargetAnim : public EggCharacterFilter { +public: + EggRetargetAnim(); + + void run(); + + void retarget_anim(EggCharacterData *char_data, EggJointData *joint_data, + int reference_model, const pset &keep_names, + EggCharacterDb &db); + + Filename _reference_filename; + vector_string _keep_joints; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggTextureCards.cxx b/pandatool/src/eggprogs/eggTextureCards.cxx new file mode 100644 index 00000000..255b145a --- /dev/null +++ b/pandatool/src/eggprogs/eggTextureCards.cxx @@ -0,0 +1,492 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTextureCards.cxx + * @author drose + * @date 2001-02-21 + */ + +#include "eggTextureCards.h" + +#include "eggGroup.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggTexture.h" +#include "eggPolygon.h" +#include "pnmImageHeader.h" + +#include + +using std::string; + +/** + * + */ +EggTextureCards:: +EggTextureCards() : EggWriter(true, true) { + set_program_brief("generate an .egg file containing texture cards"); + set_program_description + ("egg-texture-cards generates an egg file consisting of several " + "square polygons, one for each texture name that appears on the " + "command line.\n\n" + + "This is a handy thing to have for importing texture images through " + "egg-palettize, even when those textures do not appear on any real " + "geometry; it can also be used for creating a lot of simple polygons " + "for rendering click buttons and similar interfaces."); + + clear_runlines(); + add_runline("[opts] texture [texture ...] output.egg"); + add_runline("[opts] -o output.egg texture [texture ...]"); + add_runline("[opts] texture [texture ...] >output.egg"); + + add_option + ("g", "left,right,bottom,top", 0, + "Specifies the geometry of each polygon. The default is a unit polygon " + "centered on the origin: -0.5,0.5,-0.5,0.5. Polygons are always created " + "on the X-Y plane. If -p is not also specified, all polygons will be " + "the same size and shape.", + &EggTextureCards::dispatch_double_quad, nullptr, &_polygon_geometry[0]); + + add_option + ("p", "xpixels,ypixels", 0, + "Indicates that polygons should be sized in proportion to the pixel " + "size of the texture image. This will potentially create a " + "different size and shape polygon for each texture. The coordinate " + "pair represents the image size in " + "pixels that will exactly fill up the polygon described with -g (or the " + "default polygon if -g is not specified); smaller images will be " + "given proportionately smaller polygons, and larger images will be " + "given proportionately larger polygons.", + &EggTextureCards::dispatch_double_pair, &_got_pixel_scale, &_pixel_scale[0]); + + add_option + ("suffix", "string", 0, + "Normally, each polygon is given a name based on the basename of its " + "corresponding texture's filename (without the filename extension). " + "This option specifies an ignorable suffix in the texture filename(s); " + "if this suffix is present, it is not included in the polygon's name. " + "This option may be repeated multiple times.", + &EggTextureCards::dispatch_vector_string, nullptr, &_suffixes); + + add_option + ("c", "r,g,b[,a]", 0, + "Specifies the color of each polygon. The default is white: 1,1,1,1.", + &EggTextureCards::dispatch_color, nullptr, &_polygon_color[0]); + + add_option + ("wm", "wrap", 0, + "Indicates the wrap mode of the texture: \"repeat\", \"clamp\", " + "or any of the other modes supported by egg syntax. " + "The default is to leave this unspecified.", + &EggTextureCards::dispatch_wrap_mode, nullptr, &_wrap_mode); + + add_option + ("wmu", "wrap_u", 0, + "Indicates the wrap mode of the texture in the U direction. This " + "overrides -wm, if specified.", + &EggTextureCards::dispatch_wrap_mode, nullptr, &_wrap_u); + + add_option + ("wmv", "wrap_v", 0, + "Indicates the wrap mode of the texture in the V direction. This " + "overrides -wm, if specified.", + &EggTextureCards::dispatch_wrap_mode, nullptr, &_wrap_v); + + add_option + ("minf", "filter", 0, + "Indicates the minfilter mode of the texture: \"linear\", \"mipmap\", " + "or any of the other modes supported by egg syntax. " + "The default is to leave this unspecified.", + &EggTextureCards::dispatch_filter_type, nullptr, &_minfilter); + + add_option + ("magf", "filter", 0, + "Indicates the magfilter mode of the texture: \"linear\" or \"nearest\". " + "The default is to leave this unspecified.", + &EggTextureCards::dispatch_filter_type, nullptr, &_magfilter); + + add_option + ("aniso", "degree", 0, + "Indicates the anisotropic degree of the texture. " + "The default is to leave this unspecified.", + &EggTextureCards::dispatch_int, &_got_aniso_degree, &_aniso_degree); + + add_option + ("ql", "[default | fastest | normal | best]", 0, + "Specifies the quality level of the texture. This mainly affects " + "the tinydisplay software renderer.", + &EggTextureCards::dispatch_quality_level, nullptr, &_quality_level); + + add_option + ("f", "format", 0, + "Indicates the format for all textures: typical choices are \"rgba12\" " + "or \"rgb5\" or \"alpha\". The default is to leave this unspecified.", + &EggTextureCards::dispatch_format, nullptr, &_format); + + add_option + ("f1", "format", 0, + "Indicates the format for one-channel textures only. If specified, this " + "overrides the format specified by -f.", + &EggTextureCards::dispatch_format, nullptr, &_format_1); + + add_option + ("f2", "format", 0, + "Indicates the format for two-channel textures only. If specified, this " + "overrides the format specified by -f.", + &EggTextureCards::dispatch_format, nullptr, &_format_2); + + add_option + ("f3", "format", 0, + "Indicates the format for three-channel textures only. If specified, this " + "overrides the format specified by -f.", + &EggTextureCards::dispatch_format, nullptr, &_format_3); + + add_option + ("f4", "format", 0, + "Indicates the format for four-channel textures only. If specified, this " + "overrides the format specified by -f.", + &EggTextureCards::dispatch_format, nullptr, &_format_4); + + add_option + ("b", "", 0, + "Make the textured polygons backfaced (two-sided).", + &EggTextureCards::dispatch_none, &_apply_bface); + + add_option + ("fps", "frame-rate", 0, + "Normally, all of the texture cards are created as a series of nodes " + "beneath a SequenceNode. This allows all of the cards to be viewed, " + "one at a time, if the output file is loaded in pview. It also has the " + "nice side-effect of creating an automatic texture flip that can be " + "used directly by applications; use this parameter to specify the " + "frame rate of that texture flip.", + &EggTextureCards::dispatch_double, nullptr, &_frame_rate); + + add_option + ("noexist", "", 0, + "Don't treat it as an error if the input file references pathnames " + "(e.g. textures) that don't exist. Normally, this will be flagged as " + "an error and the command aborted; with this option, an egg file will " + "be generated anyway, referencing pathnames that do not exist.", + &EggTextureCards::dispatch_none, &_noexist); + + _polygon_geometry.set(-0.5, 0.5, -0.5, 0.5); + _polygon_color.set(1.0, 1.0, 1.0, 1.0); + _wrap_mode = EggTexture::WM_unspecified; + _wrap_u = EggTexture::WM_unspecified; + _wrap_v = EggTexture::WM_unspecified; + _minfilter = EggTexture::FT_unspecified; + _magfilter = EggTexture::FT_unspecified; + _aniso_degree = 0; + _quality_level = EggTexture::QL_unspecified; + _format = EggTexture::F_unspecified; + _format_1 = EggTexture::F_unspecified; + _format_2 = EggTexture::F_unspecified; + _format_3 = EggTexture::F_unspecified; + _format_4 = EggTexture::F_unspecified; + _frame_rate = 2.0; +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggTextureCards:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 0)) { + return false; + } + + if (args.empty()) { + nout << "No texture names specified on the command line.\n"; + return false; + } + + ProgramBase::Args::iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + _texture_names.push_back(Filename::from_os_specific(*ai)); + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a WrapMode string. The data pointer is to a WrapMode + * enum variable. + */ +bool EggTextureCards:: +dispatch_wrap_mode(const string &opt, const string &arg, void *var) { + EggTexture::WrapMode *wmp = (EggTexture::WrapMode *)var; + + *wmp = EggTexture::string_wrap_mode(arg); + if (*wmp == EggTexture::WM_unspecified) { + // An unknown string. Let's check for our special cases. + if (arg == "r") { + *wmp = EggTexture::WM_repeat; + } else if (arg == "c") { + *wmp = EggTexture::WM_clamp; + } else { + nout << "Invalid wrap mode parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a FilterType string. The data pointer is to a + * FilterType enum variable. + */ +bool EggTextureCards:: +dispatch_filter_type(const string &opt, const string &arg, void *var) { + EggTexture::FilterType *ftp = (EggTexture::FilterType *)var; + + *ftp = EggTexture::string_filter_type(arg); + if (*ftp == EggTexture::FT_unspecified) { + // An unknown string. + nout << "Invalid filter type parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a QualityLevel string. The data pointer is to a + * QualityLevel enum variable. + */ +bool EggTextureCards:: +dispatch_quality_level(const string &opt, const string &arg, void *var) { + EggTexture::QualityLevel *qlp = (EggTexture::QualityLevel *)var; + + *qlp = EggTexture::string_quality_level(arg); + if (*qlp == EggTexture::QL_unspecified) { + nout << "Invalid quality level parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a Format string. The data pointer is to a Format enum + * variable. + */ +bool EggTextureCards:: +dispatch_format(const string &opt, const string &arg, void *var) { + EggTexture::Format *fp = (EggTexture::Format *)var; + + *fp = EggTexture::string_format(arg); + if (*fp == EggTexture::F_unspecified) { + nout << "Invalid format parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + + +/** + * Reads the texture image header to determine its size, and based on this + * size, computes the appropriate left,right,bottom,top geometry of the card + * that correspond to this texture. + * + * Returns true if successful, or false if the texture cannot be read. + */ +bool EggTextureCards:: +scan_texture(const Filename &filename, LVecBase4d &geometry, + int &num_channels) { + PNMImageHeader header; + if (!header.read_header(filename)) { + nout << "Unable to read image " << filename << "\n"; + return false; + } + + num_channels = header.get_num_channels(); + + double xscale = header.get_x_size() / _pixel_scale[0]; + double yscale = header.get_y_size() / _pixel_scale[1]; + + geometry.set(_polygon_geometry[0] * xscale, + _polygon_geometry[1] * xscale, + _polygon_geometry[2] * yscale, + _polygon_geometry[3] * yscale); + return true; +} + +/** + * Creates a set of four vertices for the polygon according to the + * left,right,bottom,top geometry. + */ +void EggTextureCards:: +make_vertices(const LPoint4d &geometry, EggVertexPool *vpool, + EggVertex *&v1, EggVertex *&v2, EggVertex *&v3, EggVertex *&v4) { + // 1 4 2 3 + + v1 = vpool->make_new_vertex + (LPoint3d(geometry[0], geometry[3], 0.0)); + v2 = vpool->make_new_vertex + (LPoint3d(geometry[0], geometry[2], 0.0)); + v3 = vpool->make_new_vertex + (LPoint3d(geometry[1], geometry[2], 0.0)); + v4 = vpool->make_new_vertex + (LPoint3d(geometry[1], geometry[3], 0.0)); + + v1->set_uv(LTexCoordd(0.0, 1.0)); + v2->set_uv(LTexCoordd(0.0, 0.0)); + v3->set_uv(LTexCoordd(1.0, 0.0)); + v4->set_uv(LTexCoordd(1.0, 1.0)); +} + +/** + * + */ +void EggTextureCards:: +run() { + // First, create an enclosing group and a vertex pool with four vertices. + // We can use the same four vertices on all polygons. + bool all_ok = true; + + EggGroup *group = new EggGroup(); + _data->add_child(group); + + // If we have more than one tile, make the group a sequence, as a + // convenience. If we view the egg file directly we can see all the tiles + // one at a time. + if (_texture_names.size() > 1) { + group->set_switch_flag(true); + group->set_switch_fps(_frame_rate); + } + + EggVertexPool *vpool = new EggVertexPool("vpool"); + group->add_child(vpool); + + EggVertex *v1, *v2, *v3, *v4; + + bool got_pixel_scale = _got_pixel_scale; + if (!got_pixel_scale) { + // If we don't have a per-texture pixel scale, all the polygons will be + // the same size, and hence may all share the same four vertices. + make_vertices(_polygon_geometry, vpool, v1, v2, v3, v4); + } + + // Now, create a texture reference and a polygon for each texture. + + vector_string::const_iterator ti; + for (ti = _texture_names.begin(); ti != _texture_names.end(); ++ti) { + Filename filename = (*ti); + string name = filename.get_basename_wo_extension(); + + // Strip off any suffixes from the name. + vector_string::const_iterator si; + for (si = _suffixes.begin(); si != _suffixes.end(); ++si) { + const string &suffix = (*si); + int prefix = (int)name.length() - (int)suffix.length(); + if (prefix > 0 && name.substr(prefix) == suffix) { + name = name.substr(0, prefix); + } + } + + // Read in the texture header and determine its size. + LVecBase4d geometry; + int num_channels; + bool texture_ok = scan_texture(filename, geometry, num_channels); + if (!texture_ok) { + all_ok = false; + } + + if (got_pixel_scale) { + if (texture_ok) { + make_vertices(geometry, vpool, v1, v2, v3, v4); + } else { + make_vertices(_polygon_geometry, vpool, v1, v2, v3, v4); + } + } + + EggTexture *tref = new EggTexture(name, filename); + tref->set_wrap_mode(_wrap_mode); + tref->set_wrap_u(_wrap_u); + tref->set_wrap_v(_wrap_v); + tref->set_minfilter(_minfilter); + tref->set_magfilter(_magfilter); + if (_got_aniso_degree) { + tref->set_anisotropic_degree(_aniso_degree); + } + tref->set_quality_level(_quality_level); + + if (texture_ok) { + switch (num_channels) { + case 1: + tref->set_format(_format_1); + break; + + case 2: + tref->set_format(_format_2); + break; + + case 3: + tref->set_format(_format_3); + break; + + case 4: + tref->set_format(_format_4); + break; + } + } + + if (tref->get_format() == EggTexture::F_unspecified) { + tref->set_format(_format); + } + + group->add_child(tref); + + // Each polygon gets placed in its own sub-group. This will make pulling + // them out by name at runtime possible. + EggGroup *sub_group = new EggGroup(name); + group->add_child(sub_group); + EggPolygon *poly = new EggPolygon(); + sub_group->add_child(poly); + poly->set_texture(tref); + poly->set_color(_polygon_color); + if (_apply_bface){ + poly->set_bface_flag(1); + } + + poly->add_vertex(v1); + poly->add_vertex(v2); + poly->add_vertex(v3); + poly->add_vertex(v4); + } + + // Done! + if (all_ok || _noexist) { + write_egg_file(); + } else { + nout << "Some textures not found; not generating egg file.\n"; + exit(1); + } +} + + +int main(int argc, char *argv[]) { + EggTextureCards prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggTextureCards.h b/pandatool/src/eggprogs/eggTextureCards.h new file mode 100644 index 00000000..c30e926d --- /dev/null +++ b/pandatool/src/eggprogs/eggTextureCards.h @@ -0,0 +1,74 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTextureCards.h + * @author drose + * @date 2001-02-21 + */ + +#ifndef EGGTEXTURECARDS_H +#define EGGTEXTURECARDS_H + +#include "pandatoolbase.h" + +#include "eggWriter.h" +#include "eggTexture.h" +#include "luse.h" +#include "vector_string.h" + +class EggVertexPool; +class EggVertex; + +/** + * Generates an egg file featuring a number of polygons, one for each named + * texture. This is a support program for getting textures through egg- + * palettize. + */ +class EggTextureCards : public EggWriter { +public: + EggTextureCards(); + +protected: + virtual bool handle_args(Args &args); + + static bool dispatch_wrap_mode(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_filter_type(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_quality_level(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_format(const std::string &opt, const std::string &arg, void *var); + +private: + bool scan_texture(const Filename &filename, LVecBase4d &geometry, + int &num_channels); + void make_vertices(const LPoint4d &geometry, EggVertexPool *vpool, + EggVertex *&v1, EggVertex *&v2, EggVertex *&v3, EggVertex *&v4); + +public: + void run(); + + LVecBase4d _polygon_geometry; + LVecBase2d _pixel_scale; + bool _got_pixel_scale; + vector_string _suffixes; + LColor _polygon_color; + vector_string _texture_names; + EggTexture::WrapMode _wrap_mode; + EggTexture::WrapMode _wrap_u; + EggTexture::WrapMode _wrap_v; + EggTexture::FilterType _minfilter; + EggTexture::FilterType _magfilter; + bool _got_aniso_degree; + int _aniso_degree; + EggTexture::QualityLevel _quality_level; + EggTexture::Format _format; + EggTexture::Format _format_1, _format_2, _format_3, _format_4; + bool _apply_bface; + double _frame_rate; + bool _noexist; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggToC.cxx b/pandatool/src/eggprogs/eggToC.cxx new file mode 100644 index 00000000..bafc37fd --- /dev/null +++ b/pandatool/src/eggprogs/eggToC.cxx @@ -0,0 +1,373 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToC.cxx + * @author drose + * @date 2001-08-03 + */ + +#include "eggToC.h" + +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggPolygon.h" +#include "eggPrimitive.h" +#include "eggGroupNode.h" +#include "eggPolysetMaker.h" +#include "eggBin.h" +#include "string_utils.h" + +using std::ostream; +using std::string; + +/** + * + */ +EggToC:: +EggToC() : + EggToSomething("C", ".c", true, true) +{ + set_program_brief("convert .egg geometry into compilable C tables"); + set_program_description + ("This program reads Egg files and outputs code that will almost " + "compile as a C or C++ program. You get to define the data structures " + "for the program after the fact; the program only generates tables " + "of vertices and polygons."); + + // -f is always in effect for egg2c. It doesn't make sense to provide it as + // an option to the user. + remove_option("f"); + + add_option + ("v", "", 0, + "Generate a table of vertex positions.", + &EggToC::dispatch_none, &_vertices); + + add_option + ("u", "", 0, + "Generate a table of UV's per each vertex.", + &EggToC::dispatch_none, &_uvs); + + add_option + ("vn", "", 0, + "Generate a table of normals per each vertex.", + &EggToC::dispatch_none, &_vertex_normals); + + add_option + ("vc", "", 0, + "Generate a table of colors per each vertex.", + &EggToC::dispatch_none, &_vertex_colors); + + add_option + ("p", "", 0, + "Generate a table of polygons that index into the above tables.", + &EggToC::dispatch_none, &_polygons); + + add_option + ("pn", "", 0, + "Generate a table of normals per each polygon.", + &EggToC::dispatch_none, &_polygon_normals); + + add_option + ("pc", "", 0, + "Generate a table of colors per each polygon.", + &EggToC::dispatch_none, &_polygon_colors); + + add_option + ("t", "", 0, + "Output only triangles by subdividing higher-order polygons.", + &EggToC::dispatch_none, &_triangulate_polygons); +} + +/** + * + */ +void EggToC:: +run() { + nout << "Removing invalid primitives.\n"; + int num_removed = _data->remove_invalid_primitives(true); + nout << " (" << num_removed << " removed.)\n"; + + if (_triangulate_polygons) { + nout << "Triangulating polygons.\n"; + int num_produced = _data->triangulate_polygons(~0); + nout << " (" << num_produced << " triangles produced.)\n"; + } + + _data->apply_texmats(); + _data->flatten_transforms(); + _data->remove_unused_vertices(true); + + // Collect all the polygons together into polysets. + EggPolysetMaker pmaker; + pmaker.set_properties(0); + pmaker.make_bins(_data); + + get_output() + << "/*\n" + << " * Generated by:\n" + << " * " << get_exec_command() << "\n" + << " *\n" + << " */\n\n"; + + _next_vpool_index = 0; + _next_bin_index = 0; + traverse(_data); +} + +/** + * + */ +void EggToC:: +traverse(EggNode *node) { + if (node->is_of_type(EggVertexPool::get_class_type())) { + write_vertex_pool(DCAST(EggVertexPool, node)); + + } else if (node->is_of_type(EggBin::get_class_type())) { + write_bin(DCAST(EggBin, node)); + + } else if (node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *group = DCAST(EggGroupNode, node); + + EggGroupNode::const_iterator ci; + for (ci = group->begin(); ci != group->end(); ++ci) { + traverse(*ci); + } + } +} + +/** + * + */ +void EggToC:: +write_vertex_pool(EggVertexPool *vpool) { + int highest_index = vpool->get_highest_index(); + int i; + + ostream &out = get_output(); + out << "/* Vertex pool index " << _next_vpool_index + << ": " << vpool->get_name() << " */\n"; + _vertex_pools[vpool] = _next_vpool_index; + _next_vpool_index++; + + if (_vertices) { + out << "/* Vertex definition for " << vpool->get_name() << " */\n" + << "vertex vertices_" << vpool->get_name() << "[" << highest_index + << "] = {\n"; + for (i = 0; i < highest_index; i++) { + EggVertex *vert = vpool->get_vertex(i); + if (vert == nullptr) { + out << " vertex(), /* " << i << " */\n"; + } else { + LPoint4d p = vert->get_pos4(); + switch (vert->get_num_dimensions()) { + case 1: + out << " vertex(" << p[0] << "), /* " << i << " */\n"; + break; + + case 2: + out << " vertex(" << p[0] << ", " << p[1] + << "), /* " << i << " */\n"; + break; + + case 3: + out << " vertex(" << p[0] << ", " << p[1] << ", " << p[2] + << "), /* " << i << " */\n"; + break; + + case 4: + out << " vertex(" << p[0] << ", " << p[1] << ", " << p[2] + << ", " << p[3] << "), /* " << i << " */\n"; + break; + + default: + out << " vertex(), /* error */\n"; + } + } + } + out << "};\n\n"; + } + + if (_uvs) { + out << "/* UV's for " << vpool->get_name() << " */\n" + << "uv uvs_" << vpool->get_name() << "[" << highest_index + << "] = {\n"; + for (i = 0; i < highest_index; i++) { + EggVertex *vert = vpool->get_vertex(i); + if (vert == nullptr || !vert->has_uv()) { + out << " uv(), /* " << i << " */\n"; + } else { + LTexCoordd uv = vert->get_uv(); + out << " uv(" << uv[0] << ", " << uv[1] + << "), /* " << i << " */\n"; + } + } + out << "};\n\n"; + } + + if (_vertex_normals) { + out << "/* Vertex normals for " << vpool->get_name() << " */\n" + << "normal normals_" << vpool->get_name() << "[" << highest_index + << "] = {\n"; + for (i = 0; i < highest_index; i++) { + EggVertex *vert = vpool->get_vertex(i); + if (vert == nullptr || !vert->has_normal()) { + out << " normal(), /* " << i << " */\n"; + } else { + LNormald n = vert->get_normal(); + out << " normal(" << n[0] << ", " << n[1] << ", " << n[2] + << "), /* " << i << " */\n"; + } + } + out << "};\n\n"; + } + + if (_vertex_colors) { + out << "/* Vertex colors for " << vpool->get_name() << " */\n" + << "color colors_" << vpool->get_name() << "[" << highest_index + << "] = {\n"; + for (i = 0; i < highest_index; i++) { + EggVertex *vert = vpool->get_vertex(i); + if (vert == nullptr || !vert->has_color()) { + out << " color(), /* " << i << " */\n"; + } else { + LColor c = vert->get_color(); + out << " color(" << c[0] << ", " << c[1] << ", " << c[2] + << ", " << c[3] << "), /* " << i << " */\n"; + } + } + out << "};\n\n"; + } +} + + +/** + * + */ +void EggToC:: +write_bin(EggBin *bin) { + ostream &out = get_output(); + string bin_name = bin->get_name(); + if (bin_name.empty()) { + bin_name = format_string(_next_bin_index); + _next_bin_index++; + } + + out << "/* Polygon group " << bin_name << " */\n"; + + size_t num_children = bin->size(); + + if (_polygons) { + out << "/* Polygon definitions for " << bin_name << " */\n"; + string prim_type = "polygon"; + if (_triangulate_polygons) { + prim_type = "triangle"; + } + + out << prim_type << " polys_" << bin_name << "[" << num_children + << "] = {\n"; + + if (_triangulate_polygons) { + out << " /* vpool index, vertex0, vertex1, vertex2 */\n"; + } else { + out << " /* vpool index, num vertices, vertex0, vertex1, vertex2, ... */\n"; + } + + EggGroupNode::const_iterator ci; + size_t prim_index = 0; + for (ci = bin->begin(); ci != bin->end(); ++ci) { + EggNode *child = (*ci); + if (!child->is_of_type(EggPrimitive::get_class_type())) { + out << " " << prim_type << "(), /* error */\n"; + } else { + EggPrimitive *prim = DCAST(EggPrimitive, child); + EggVertexPool *vpool = prim->get_pool(); + int vpool_index = -1; + VertexPools::const_iterator pi = _vertex_pools.find(vpool); + if (pi != _vertex_pools.end()) { + vpool_index = (*pi).second; + } + + out << " " << prim_type << "(" << vpool_index; + if (!_triangulate_polygons) { + out << ", " << prim->size(); + } + EggPrimitive::const_iterator vi; + for (vi = prim->begin(); vi != prim->end(); ++vi) { + EggVertex *vert = (*vi); + out << ", " << vert->get_index(); + } + out << "), /* " << prim_index << " */\n"; + prim_index++; + } + } + out << "};\n\n"; + } + + if (_polygon_normals) { + ostream &out = get_output(); + out << "/* Polygon normals for " << bin_name << " */\n"; + out << "normal polys_" << bin_name << "[" << num_children + << "] = {\n"; + + EggGroupNode::const_iterator ci; + size_t prim_index = 0; + for (ci = bin->begin(); ci != bin->end(); ++ci) { + EggNode *child = (*ci); + if (!child->is_of_type(EggPrimitive::get_class_type())) { + out << " normal(), /* error */\n"; + } else { + EggPrimitive *prim = DCAST(EggPrimitive, child); + if (!prim->has_normal()) { + out << " normal(), /* " << prim_index << " */\n"; + } else { + LNormald n = prim->get_normal(); + out << " normal(" << n[0] << ", " << n[1] << ", " << n[2] + << "), /* " << prim_index << " */\n"; + } + prim_index++; + } + } + out << "};\n\n"; + } + + if (_polygon_colors) { + ostream &out = get_output(); + out << "/* Polygon colors for " << bin_name << " */\n"; + out << "color polys_" << bin_name << "[" << num_children + << "] = {\n"; + + EggGroupNode::const_iterator ci; + size_t prim_index = 0; + for (ci = bin->begin(); ci != bin->end(); ++ci) { + EggNode *child = (*ci); + if (!child->is_of_type(EggPrimitive::get_class_type())) { + out << " color(), /* error */\n"; + } else { + EggPrimitive *prim = DCAST(EggPrimitive, child); + if (!prim->has_color()) { + out << " color(), /* " << prim_index << " */\n"; + } else { + LColor c = prim->get_color(); + out << " color(" << c[0] << ", " << c[1] << ", " << c[2] + << ", " << c[3] << "), /* " << prim_index << " */\n"; + } + prim_index++; + } + } + out << "};\n\n"; + } +} + + +int main(int argc, char *argv[]) { + EggToC prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggToC.h b/pandatool/src/eggprogs/eggToC.h new file mode 100644 index 00000000..9c5f328f --- /dev/null +++ b/pandatool/src/eggprogs/eggToC.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToC.h + * @author drose + * @date 2001-08-03 + */ + +#ifndef EGGTOC_H +#define EGGTOC_H + +#include "pandatoolbase.h" + +#include "eggToSomething.h" + +#include "pmap.h" + +class EggNode; +class EggVertexPool; +class EggBin; + +/** + * + */ +class EggToC : public EggToSomething { +public: + EggToC(); + + void run(); + + void traverse(EggNode *node); + void write_vertex_pool(EggVertexPool *vpool); + void write_bin(EggBin *bin); + + bool _vertices; + bool _uvs; + bool _vertex_normals; + bool _vertex_colors; + bool _polygons; + bool _polygon_normals; + bool _polygon_colors; + + bool _triangulate_polygons; + + typedef pmap VertexPools; + VertexPools _vertex_pools; + int _next_vpool_index; + int _next_bin_index; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggTopstrip.cxx b/pandatool/src/eggprogs/eggTopstrip.cxx new file mode 100644 index 00000000..2a46431f --- /dev/null +++ b/pandatool/src/eggprogs/eggTopstrip.cxx @@ -0,0 +1,349 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTopstrip.cxx + * @author drose + * @date 2001-02-23 + */ + +#include "eggTopstrip.h" + +#include "dcast.h" +#include "eggJointData.h" +#include "eggCharacterCollection.h" +#include "eggCharacterData.h" +#include "eggCharacterDb.h" +#include "eggJointPointer.h" +#include "eggTable.h" +#include "compose_matrix.h" + +/** + * + */ +EggTopstrip:: +EggTopstrip() { + add_path_replace_options(); + add_path_store_options(); + + set_program_brief("unapplies animation from a joint in an .egg file"); + set_program_description + ("egg-topstrip reads a character model and its associated animation " + "files, and unapplies the animation from one of the top joints. " + "This effectively freezes that particular joint, and makes the rest " + "of the character relative to that joint.\n\n" + + "This is a particularly useful thing to do to generate character " + "models that can stack one on top of the other in a sensible way."); + + add_option + ("t", "name", 0, + "Specify the name of the 'top' joint, from which to draw the " + "animation channels which will be applied to the entire animation.", + &EggTopstrip::dispatch_string, nullptr, &_top_joint_name); + + add_option + ("i", "", 0, + "Invert the matrix before applying. This causes a subtractive " + "effect. This is the default unless -r is specified.", + &EggTopstrip::dispatch_true, &_got_invert_transform, &_invert_transform); + + add_option + ("n", "", 0, + "Do not invert the matrix before applying. This causes an " + "additive effect.", + &EggTopstrip::dispatch_false, &_got_invert_transform, &_invert_transform); + + add_option + ("s", "[ijkphrxyz]", 0, + "Specify the components of the transform that are to be applied. Use " + "any combination of the nine token letters: i, j, k represent the " + "three scale axes; h, p, r represent rotation; and x, y, z represent " + "translation. The default is everything: -s ijkphrxyz.", + &EggTopstrip::dispatch_string, nullptr, &_transform_channels); + + add_option + ("r", "file.egg", 0, + "Read the animation channel from the indicated egg file. If this " + "is not specified, each egg file will supply its own animation channel.", + &EggTopstrip::dispatch_filename, nullptr, &_channel_filename); + + _invert_transform = true; + _transform_channels = "ijkphrxyz"; +} + +/** + * + */ +void EggTopstrip:: +run() { + nassertv(_collection != nullptr); + nassertv(_collection->get_num_eggs() > 0); + + check_transform_channels(); + + // Get the number of characters first, in case adding the _channel_egg + // changes this. + int num_characters = _collection->get_num_characters(); + + // Determine which model and character we'll be pulling the animation + // channels from. + int from_model = -1; + + if (!_channel_filename.empty()) { + // Read in the extra egg file that we use for extracting the channels out. + PT(EggData) channel_egg = read_egg(_channel_filename); + if (channel_egg == nullptr) { + nout << "Cannot read " << _channel_filename << "\n"; + exit(1); + } + int channel_egg_index = _collection->add_egg(channel_egg); + if (channel_egg_index < 0) { + nout << _channel_filename + << " does not contain a character model or animation channel.\n"; + exit(1); + } + + from_model = _collection->get_first_model_index(channel_egg_index); + + if (!_got_invert_transform) { + // With -r, the default is not to invert the transform. + _invert_transform = false; + } + } + + // Now process each character. + EggCharacterDb db; + + int ci; + for (ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + nout << "Processing " << char_data->get_name() << "\n"; + + EggJointData *root_joint = char_data->get_root_joint(); + + // We'll read the transform to apply from this character, which will be + // the same character unless -r was specified. + EggCharacterData *from_char = char_data; + if (from_model != -1) { + from_char = _collection->get_character_by_model_index(from_model); + } + + // Determine which joint we'll use to extract the transform to apply. + EggJointData *top_joint = nullptr; + if (_top_joint_name.empty()) { + // The default top joint name is the alphabetically first joint in the + // top level. + if (root_joint->get_num_children() == 0) { + nout << "Character " << from_char->get_name() << " has no joints.\n"; + exit(1); + } + top_joint = root_joint->get_child(0); + } else { + top_joint = from_char->find_joint(_top_joint_name); + if (top_joint == nullptr) { + nout << "Character " << from_char->get_name() + << " has no joint named " << _top_joint_name << "\n"; + exit(1); + } + } + + // First, transform all the joints. + int num_children = root_joint->get_num_children(); + for (int i = 0; i < num_children; i++) { + EggJointData *joint_data = root_joint->get_child(i); + strip_anim(char_data, joint_data, from_model, from_char, top_joint, db); + } + + // We also need to transform the vertices for any models involved here. + int num_models = char_data->get_num_models(); + for (int m = 0; m < num_models; m++) { + EggNode *node = char_data->get_model_root(m); + if (!node->is_of_type(EggTable::get_class_type())) { + strip_anim_vertices(node, char_data->get_model_index(m), + from_model, top_joint, db); + } + } + } + + // Now, trigger the actual rebuilding of all the joint data. + for (ci = 0; ci < num_characters; ci++) { + EggCharacterData *char_data = _collection->get_character(ci); + char_data->get_root_joint()->do_rebuild_all(db); + } + + write_eggs(); +} + +/** + * Checks the _transform_channels string to ensure that it contains only the + * expected nine letters, or a subset. + */ +void EggTopstrip:: +check_transform_channels() { + static std::string expected = "ijkphrxyz"; + static const int num_channels = 9; + bool has_each[num_channels]; + memset(has_each, 0, num_channels * sizeof(bool)); + + for (size_t p = 0; p < _transform_channels.size(); p++) { + int i = expected.find(_transform_channels[p]); + if (i == (int)std::string::npos) { + nout << "Invalid letter for -s: " << _transform_channels[p] << "\n"; + exit(1); + } + nassertv(i < num_channels); + has_each[i] = true; + } + + _transform_channels = ""; + for (int i = 0; i < num_channels; i++) { + if (has_each[i]) { + _transform_channels += expected[i]; + } + } + + if (_transform_channels.empty()) { + nout << "No transform specified for -s.\n"; + exit(1); + } +} + + +/** + * Applies the channels from joint _top_joint in model from_model to the joint + * referenced by joint_data. + */ +void EggTopstrip:: +strip_anim(EggCharacterData *char_data, EggJointData *joint_data, + int from_model, EggCharacterData *from_char, + EggJointData *top_joint, EggCharacterDb &db) { + int num_models = joint_data->get_num_models(); + for (int i = 0; i < num_models; i++) { + int model = (from_model < 0) ? i : from_model; + if (joint_data->has_model(i)) { + if (!top_joint->has_model(model)) { + nout << "Warning: Joint " << top_joint->get_name() + << " is not defined in all models.\n"; + return; + } + + int num_into_frames = char_data->get_num_frames(i); + int num_from_frames = from_char->get_num_frames(model); + + int num_frames = std::max(num_into_frames, num_from_frames); + + EggBackPointer *back = joint_data->get_model(i); + nassertv(back != nullptr); + EggJointPointer *joint; + DCAST_INTO_V(joint, back); + + // Compute and apply the new transforms. + + int f; + for (f = 0; f < num_frames; f++) { + LMatrix4d into = joint_data->get_frame(i, f % num_into_frames); + LMatrix4d from = top_joint->get_net_frame(model, f % num_from_frames, db); + + adjust_transform(from); + + db.set_matrix(joint, EggCharacterDb::TT_rebuild_frame, + f, into * from); + } + } + } +} + +/** + * Applies the channels from joint _top_joint in model from_model to the + * vertices at egg_node. + */ +void EggTopstrip:: +strip_anim_vertices(EggNode *egg_node, int into_model, int from_model, + EggJointData *top_joint, EggCharacterDb &db) { + int model = (from_model < 0) ? into_model : from_model; + if (!top_joint->has_model(model)) { + nout << "Warning: Joint " << top_joint->get_name() + << " is not defined in all models.\n"; + return; + } + + LMatrix4d from = top_joint->get_net_frame(model, 0, db); + adjust_transform(from); + + egg_node->transform_vertices_only(from); +} + + +/** + * Adjust the transform extracted from the "top" joint according to the -s and + * -i/-n options, prior to applying it to the skeleton. + */ +void EggTopstrip:: +adjust_transform(LMatrix4d &mat) const { + if (_transform_channels.length() != 9) { + // Decompose and recompose the matrix, so we can eliminate the parts the + // user doesn't want. + + LVecBase3d scale, hpr, translate; + bool result = decompose_matrix(mat, scale, hpr, translate, _coordinate_system); + if (!result) { + nout << "Warning: skew transform in animation.\n"; + } else { + LVecBase3d new_scale(1.0, 1.0, 1.0); + LVecBase3d new_hpr(0.0, 0.0, 0.0); + LVecBase3d new_translate(0.0, 0.0, 0.0); + + for (size_t i = 0; i < _transform_channels.size(); i++) { + switch (_transform_channels[i]) { + case 'i': + new_scale[0] = scale[0]; + break; + case 'j': + new_scale[1] = scale[1]; + break; + case 'k': + new_scale[2] = scale[2]; + break; + + case 'h': + new_hpr[0] = hpr[0]; + break; + case 'p': + new_hpr[1] = hpr[1]; + break; + case 'r': + new_hpr[2] = hpr[2]; + break; + + case 'x': + new_translate[0] = translate[0]; + break; + case 'y': + new_translate[1] = translate[1]; + break; + case 'z': + new_translate[2] = translate[2]; + break; + } + } + + compose_matrix(mat, new_scale, new_hpr, new_translate, _coordinate_system); + } + } + if (_invert_transform) { + mat.invert_in_place(); + } +} + + +int main(int argc, char *argv[]) { + EggTopstrip prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggTopstrip.h b/pandatool/src/eggprogs/eggTopstrip.h new file mode 100644 index 00000000..8e04b7e0 --- /dev/null +++ b/pandatool/src/eggprogs/eggTopstrip.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTopstrip.h + * @author drose + * @date 2001-02-23 + */ + +#ifndef EGGTOPSTRIP_H +#define EGGTOPSTRIP_H + +#include "pandatoolbase.h" + +#include "eggCharacterFilter.h" +#include "luse.h" + +#include "pvector.h" + +class EggCharacterData; +class EggCharacterDb; +class EggJointData; +class EggJointPointer; + +/** + * Reads a character model and/or animations and strips out the animation from + * one of the top joints from the entire character. Particularly useful for + * generating stackable character models from separately-extracted characters. + */ +class EggTopstrip : public EggCharacterFilter { +public: + EggTopstrip(); + + void run(); + void check_transform_channels(); + + void strip_anim(EggCharacterData *char_data, EggJointData *joint_data, + int from_model, EggCharacterData *from_char, + EggJointData *top_joint, EggCharacterDb &db); + void strip_anim_vertices(EggNode *egg_node, int into_model, + int from_model, EggJointData *top_joint, + EggCharacterDb &db); + + void adjust_transform(LMatrix4d &mat) const; + + + std::string _top_joint_name; + bool _got_invert_transform; + bool _invert_transform; + std::string _transform_channels; + Filename _channel_filename; +}; + +#endif diff --git a/pandatool/src/eggprogs/eggTrans.cxx b/pandatool/src/eggprogs/eggTrans.cxx new file mode 100644 index 00000000..28d439e6 --- /dev/null +++ b/pandatool/src/eggprogs/eggTrans.cxx @@ -0,0 +1,137 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTrans.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "eggTrans.h" +#include "eggGroupUniquifier.h" + +/** + * + */ +EggTrans:: +EggTrans() { + add_path_replace_options(); + add_path_store_options(); + add_normals_options(); + add_transform_options(); + add_texture_options(); + add_delod_options(); + + set_program_brief("apply transformations and optimizations to an .egg file"); + set_program_description + ("egg-trans reads an egg file and writes an essentially equivalent " + "egg file to the standard output, or to the file specified with -o. " + "Some simple operations on the egg file are supported."); + + add_option + ("F", "", 0, + "Flatten out transforms.", + &EggTrans::dispatch_none, &_flatten_transforms); + + add_option + ("t", "", 0, + "Apply texture matrices to UV's.", + &EggTrans::dispatch_none, &_apply_texmats); + + add_option + ("T", "", 0, + "Collapse equivalent texture references.", + &EggTrans::dispatch_none, &_collapse_equivalent_textures); + + add_option + ("c", "", 0, + "Clean out degenerate polygons and unused vertices.", + &EggTrans::dispatch_none, &_remove_invalid_primitives); + + add_option + ("C", "", 0, + "Clean out higher-order polygons by subdividing into triangles.", + &EggTrans::dispatch_none, &_triangulate_polygons); + + add_option + ("mesh", "", 0, + "Mesh triangles into triangle strips. This is mainly useful as a " + "tool to visualize the work that the mesher will do, since triangles " + "are automatically meshed whenever an egg file is loaded. Note that, " + "unlike the automatic meshing at load time, you are must ensure that " + "you do not start out with multiple triangles with different attributes " + "(e.g. texture) together in the same group.", + &EggTrans::dispatch_none, &_mesh_triangles); + + add_option + ("N", "", 0, + "Standardize and uniquify group names.", + &EggTrans::dispatch_none, &_standardize_names); + +} + +/** + * + */ +void EggTrans:: +run() { + if (_remove_invalid_primitives) { + nout << "Removing invalid primitives.\n"; + int num_removed = _data->remove_invalid_primitives(true); + nout << " (" << num_removed << " removed.)\n"; + _data->remove_unused_vertices(true); + } + + if (_triangulate_polygons) { + nout << "Triangulating polygons.\n"; + int num_produced = _data->triangulate_polygons(~0); + nout << " (" << num_produced << " triangles produced.)\n"; + } + + if (_mesh_triangles) { + nout << "Meshing triangles.\n"; + _data->mesh_triangles(~0); + } + + if (_apply_texmats) { + nout << "Applying texture matrices.\n"; + _data->apply_texmats(); + _data->remove_unused_vertices(true); + } + + if (_collapse_equivalent_textures) { + nout << "Collapsing equivalent textures.\n"; + int num_removed = _data->collapse_equivalent_textures(); + nout << " (" << num_removed << " removed.)\n"; + } + + if (_flatten_transforms) { + nout << "Flattening transforms.\n"; + _data->flatten_transforms(); + _data->remove_unused_vertices(true); + } + + if (_standardize_names) { + nout << "Standardizing group names.\n"; + EggGroupUniquifier uniquifier; + uniquifier.uniquify(_data); + } + + if (!do_reader_options()) { + exit(1); + } + + write_egg_file(); +} + + +int main(int argc, char *argv[]) { + EggTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/eggprogs/eggTrans.h b/pandatool/src/eggprogs/eggTrans.h new file mode 100644 index 00000000..350ece14 --- /dev/null +++ b/pandatool/src/eggprogs/eggTrans.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggTrans.h + * @author drose + * @date 2000-02-14 + */ + +#ifndef EGGTRANS_H +#define EGGTRANS_H + +#include "pandatoolbase.h" + +#include "eggFilter.h" + +/** + * A program to read an egg file and write an equivalent egg file, possibly + * performing some minor operations along the way. + */ +class EggTrans : public EggFilter { +public: + EggTrans(); + + void run(); + + bool _flatten_transforms; + bool _apply_texmats; + bool _collapse_equivalent_textures; + bool _remove_invalid_primitives; + bool _triangulate_polygons; + bool _mesh_triangles; + bool _standardize_names; +}; + +#endif diff --git a/pandatool/src/flt/CMakeLists.txt b/pandatool/src/flt/CMakeLists.txt new file mode 100644 index 00000000..02f096b8 --- /dev/null +++ b/pandatool/src/flt/CMakeLists.txt @@ -0,0 +1,90 @@ +set(P3FLT_HEADERS + config_flt.h + fltBead.h + fltBeadID.h + fltCurve.h fltCurve.I + fltError.h + fltExternalReference.h + fltEyepoint.h + fltFace.h fltFace.I + fltGeometry.h fltGeometry.I + fltGroup.h + fltHeader.h + fltInstanceDefinition.h + fltInstanceRef.h + fltLightSourceDefinition.h + fltLocalVertexPool.h fltLocalVertexPool.I + fltLOD.h + fltMaterial.h + fltMesh.h fltMesh.I + fltMeshPrimitive.h fltMeshPrimitive.I + fltObject.h + fltOpcode.h + fltPackedColor.h fltPackedColor.I + fltRecord.h fltRecord.I + fltRecordReader.h + fltRecordWriter.h + fltTexture.h + fltTrackplane.h + fltTransformGeneralMatrix.h + fltTransformPut.h + fltTransformRecord.h + fltTransformRotateAboutEdge.h + fltTransformRotateAboutPoint.h + fltTransformRotateScale.h + fltTransformScale.h + fltTransformTranslate.h + fltUnsupportedRecord.h + fltVectorRecord.h + fltVertex.h fltVertex.I + fltVertexList.h +) + +set(P3FLT_SOURCES + config_flt.cxx + fltBead.cxx + fltBeadID.cxx + fltCurve.cxx + fltError.cxx + fltExternalReference.cxx + fltEyepoint.cxx + fltFace.cxx + fltGeometry.cxx + fltGroup.cxx + fltHeader.cxx + fltInstanceDefinition.cxx + fltInstanceRef.cxx + fltLightSourceDefinition.cxx + fltLocalVertexPool.cxx + fltLOD.cxx + fltMaterial.cxx + fltMesh.cxx + fltMeshPrimitive.cxx + fltObject.cxx + fltOpcode.cxx + fltPackedColor.cxx + fltRecord.cxx + fltRecordReader.cxx + fltRecordWriter.cxx + fltTexture.cxx + fltTrackplane.cxx + fltTransformGeneralMatrix.cxx + fltTransformPut.cxx + fltTransformRecord.cxx + fltTransformRotateAboutEdge.cxx + fltTransformRotateAboutPoint.cxx + fltTransformRotateScale.cxx + fltTransformScale.cxx + fltTransformTranslate.cxx + fltUnsupportedRecord.cxx + fltVectorRecord.cxx + fltVertex.cxx + fltVertexList.cxx +) + +composite_sources(p3flt P3FLT_SOURCES) +add_library(p3flt STATIC ${P3FLT_HEADERS} ${P3FLT_SOURCES}) +target_link_libraries(p3flt p3pandatoolbase panda) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/flt/config_flt.cxx b/pandatool/src/flt/config_flt.cxx new file mode 100644 index 00000000..1e4f0f9d --- /dev/null +++ b/pandatool/src/flt/config_flt.cxx @@ -0,0 +1,112 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_flt.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "config_flt.h" +#include "fltRecord.h" +#include "fltBead.h" +#include "fltBeadID.h" +#include "fltGroup.h" +#include "fltObject.h" +#include "fltGeometry.h" +#include "fltFace.h" +#include "fltCurve.h" +#include "fltMesh.h" +#include "fltLocalVertexPool.h" +#include "fltMeshPrimitive.h" +#include "fltVectorRecord.h" +#include "fltVertexList.h" +#include "fltLOD.h" +#include "fltInstanceDefinition.h" +#include "fltInstanceRef.h" +#include "fltHeader.h" +#include "fltVertex.h" +#include "fltMaterial.h" +#include "fltTexture.h" +#include "fltLightSourceDefinition.h" +#include "fltUnsupportedRecord.h" +#include "fltTransformRecord.h" +#include "fltTransformGeneralMatrix.h" +#include "fltTransformPut.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformRotateAboutPoint.h" +#include "fltTransformScale.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateScale.h" +#include "fltExternalReference.h" + +#include "dconfig.h" + +Configure(config_flt); +NotifyCategoryDef(flt, ""); + + +ConfigVariableBool flt_error_abort +("flt-error-abort", false, + PRC_DESC("Set this true to trigger an assertion failure (and core dump) " + "immediately when an error is detected on reading or writing a flt " + "file. This is primarily useful for debugging the flt reader itself, " + "to generate a stack trace to determine precisely at what point a flt " + + "file failed.")); + + +ConfigureFn(config_flt) { + init_libflt(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libflt() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + FltRecord::init_type(); + FltBead::init_type(); + FltBeadID::init_type(); + FltGroup::init_type(); + FltObject::init_type(); + FltGeometry::init_type(); + FltFace::init_type(); + FltCurve::init_type(); + FltMesh::init_type(); + FltLocalVertexPool::init_type(); + FltMeshPrimitive::init_type(); + FltVectorRecord::init_type(); + FltVertexList::init_type(); + FltLOD::init_type(); + FltInstanceDefinition::init_type(); + FltInstanceRef::init_type(); + FltHeader::init_type(); + FltVertex::init_type(); + FltMaterial::init_type(); + FltTexture::init_type(); + FltLightSourceDefinition::init_type(); + FltUnsupportedRecord::init_type(); + FltTransformRecord::init_type(); + FltTransformGeneralMatrix::init_type(); + FltTransformPut::init_type(); + FltTransformRotateAboutEdge::init_type(); + FltTransformRotateAboutPoint::init_type(); + FltTransformScale::init_type(); + FltTransformTranslate::init_type(); + FltTransformRotateScale::init_type(); + FltExternalReference::init_type(); +} diff --git a/pandatool/src/flt/config_flt.h b/pandatool/src/flt/config_flt.h new file mode 100644 index 00000000..789179ce --- /dev/null +++ b/pandatool/src/flt/config_flt.h @@ -0,0 +1,30 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_flt.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef CONFIG_FLT_H +#define CONFIG_FLT_H + +#include "pandatoolbase.h" + +#include "notifyCategoryProxy.h" +#include "configVariableBool.h" + +NotifyCategoryDeclNoExport(flt); + +extern ConfigVariableBool flt_error_abort; + +extern void init_libflt(); + +static const int header_size = 4; + +#endif diff --git a/pandatool/src/flt/fltBead.cxx b/pandatool/src/flt/fltBead.cxx new file mode 100644 index 00000000..8cc21f8c --- /dev/null +++ b/pandatool/src/flt/fltBead.cxx @@ -0,0 +1,357 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltBead.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltBead.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltTransformGeneralMatrix.h" +#include "fltTransformPut.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformRotateAboutPoint.h" +#include "fltTransformScale.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateScale.h" +#include "config_flt.h" + +#include "dcast.h" + +#include + +TypeHandle FltBead::_type_handle; + +/** + * + */ +FltBead:: +FltBead(FltHeader *header) : FltRecord(header) { + _has_transform = false; + _transform = LMatrix4d::ident_mat(); + _replicate_count = 0; +} + +/** + * Returns true if the bead has been transformed, false otherwise. If this + * returns true, get_transform() will return the single-precision net + * transformation, and get_num_transform_steps() will return nonzero. + */ +bool FltBead:: +has_transform() const { + return _has_transform; +} + +/** + * Returns the single-precision 4x4 matrix that represents the transform + * applied to this bead, or the identity matrix if the bead has not been + * transformed. + */ +const LMatrix4d &FltBead:: +get_transform() const { + return _has_transform ? _transform : LMatrix4d::ident_mat(); +} + +/** + * Replaces the transform matrix on this bead. This implicitly removes all of + * the transform steps added previously, and replaces them with a single 4x4 + * general matrix transform step. + */ +void FltBead:: +set_transform(const LMatrix4d &mat) { + clear_transform(); + FltTransformGeneralMatrix *step = new FltTransformGeneralMatrix(_header); + step->set_matrix(mat); + add_transform_step(step); +} + +/** + * Removes any transform matrix and all transform steps on this bead. + */ +void FltBead:: +clear_transform() { + _has_transform = false; + _transform = LMatrix4d::ident_mat(); + _transform_steps.clear(); +} + +/** + * Returns the number of individual steps that define the net transform on + * this bead as returned by set_transform(). Each step is a single + * transformation; the concatenation of all transformations will produce the + * matrix represented by set_transform(). + */ +int FltBead:: +get_num_transform_steps() const { + return _transform_steps.size(); +} + +/** + * Returns the nth individual step that defines the net transform on this + * bead. See get_num_transform_steps(). + */ +FltTransformRecord *FltBead:: +get_transform_step(int n) { + nassertr(n >= 0 && n < (int)_transform_steps.size(), + nullptr); + return _transform_steps[n]; +} + +/** + * Returns the nth individual step that defines the net transform on this + * bead. See get_num_transform_steps(). + */ +const FltTransformRecord *FltBead:: +get_transform_step(int n) const { + nassertr(n >= 0 && n < (int)_transform_steps.size(), + nullptr); + return _transform_steps[n]; +} + +/** + * Applies the indicated transform step to the net transformation applied to + * the bead. + */ +void FltBead:: +add_transform_step(FltTransformRecord *record) { + if (!_has_transform) { + _has_transform = true; + _transform = record->get_matrix(); + } else { + _transform = record->get_matrix() * _transform; + } + _transform_steps.push_back(record); +} + +/** + * Returns the replicate count of this bead. If this is nonzero, it means + * that the bead is implicitly copied this number of additional times (for + * replicate_count + 1 total copies), applying the transform on this bead for + * each copy. In this case, the transform does *not* apply to the initial + * copy of the bead. + */ +int FltBead:: +get_replicate_count() const { + return _replicate_count; +} + +/** + * Changes the replicate count of this bead. If you are setting the replicate + * count to some nonzero number, you must also set a transform on the bead. + * See set_replicate_count(). + */ +void FltBead:: +set_replicate_count(int count) { + _replicate_count = count; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltBead:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + return true; +} + +/** + * Checks whether the given bead, which follows this bead sequentially in the + * file, is an ancillary record of this bead. If it is, extracts the relevant + * information and returns true; otherwise, leaves it alone and returns false. + */ +bool FltBead:: +extract_ancillary(FltRecordReader &reader) { + FltTransformRecord *step = nullptr; + + switch (reader.get_opcode()) { + case FO_transform_matrix: + return extract_transform_matrix(reader); + + case FO_general_matrix: + step = new FltTransformGeneralMatrix(_header); + break; + + case FO_put: + step = new FltTransformPut(_header); + break; + + case FO_rotate_about_edge: + step = new FltTransformRotateAboutEdge(_header); + break; + + case FO_rotate_about_point: + step = new FltTransformRotateAboutPoint(_header); + break; + + case FO_scale: + step = new FltTransformScale(_header); + break; + + case FO_translate: + step = new FltTransformTranslate(_header); + break; + + case FO_rotate_and_scale: + step = new FltTransformRotateScale(_header); + break; + + case FO_replicate: + return extract_replicate_count(reader); + + default: + return FltRecord::extract_ancillary(reader); + } + + // A transform step. + nassertr(step != nullptr, false); + if (!step->extract_record(reader)) { + return false; + } + _transform_steps.push_back(DCAST(FltTransformRecord, step)); + + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltBead:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + return true; +} + +/** + * Writes whatever ancillary records are required for this record. Returns + * FE_ok on success, or something else if there is some error. + */ +FltError FltBead:: +write_ancillary(FltRecordWriter &writer) const { + if (_has_transform) { + FltError result = write_transform(writer); + if (result != FE_ok) { + return result; + } + } + if (_replicate_count != 0) { + FltError result = write_replicate_count(writer); + if (result != FE_ok) { + return result; + } + } + + + return FltRecord::write_ancillary(writer); +} + +/** + * Reads a transform matrix ancillary bead. This defines the net + * transformation that has been applied to the bead, and precedes the set of + * individual transform steps that define how this net transform was computed. + */ +bool FltBead:: +extract_transform_matrix(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_transform_matrix, false); + DatagramIterator &iterator = reader.get_iterator(); + + LMatrix4d matrix; + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + matrix(r, c) = iterator.get_be_float32(); + } + } + check_remaining_size(iterator); + + _transform_steps.clear(); + _has_transform = true; + _transform = matrix; + + return true; +} + +/** + * Reads a replicate count ancillary bead. + */ +bool FltBead:: +extract_replicate_count(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_replicate, false); + DatagramIterator &iterator = reader.get_iterator(); + + _replicate_count = iterator.get_be_int16(); + iterator.skip_bytes(2); + + check_remaining_size(iterator); + return true; +} + +/** + * Writes out the transformation and all of its defining steps. + */ +FltError FltBead:: +write_transform(FltRecordWriter &writer) const { + // First, write out the initial transform indication. + writer.set_opcode(FO_transform_matrix); + Datagram &datagram = writer.update_datagram(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_transform(r, c)); + } + } + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + + // Now, write out each of the steps of the transform. + Transforms::const_iterator ti; + for (ti = _transform_steps.begin(); ti != _transform_steps.end(); ++ti) { + if (!(*ti)->build_record(writer)) { + assert(!flt_error_abort); + return FE_invalid_record; + } + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +/** + * Writes out the replicate count, if needed. + */ +FltError FltBead:: +write_replicate_count(FltRecordWriter &writer) const { + if (_replicate_count != 0) { + writer.set_opcode(FO_replicate); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_replicate_count); + datagram.pad_bytes(2); + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} diff --git a/pandatool/src/flt/fltBead.h b/pandatool/src/flt/fltBead.h new file mode 100644 index 00000000..d1b98779 --- /dev/null +++ b/pandatool/src/flt/fltBead.h @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltBead.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTBEAD_H +#define FLTBEAD_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" +#include "fltTransformRecord.h" + +#include "luse.h" + +/** + * A base class for any of a broad family of flt records that represent + * particular beads in the hierarchy. These are things like group beads and + * object beads, as opposed to things like push and pop or comment records. + */ +class FltBead : public FltRecord { +public: + FltBead(FltHeader *header); + + bool has_transform() const; + const LMatrix4d &get_transform() const; + void set_transform(const LMatrix4d &mat); + void clear_transform(); + + int get_num_transform_steps() const; + FltTransformRecord *get_transform_step(int n); + const FltTransformRecord *get_transform_step(int n) const; + void add_transform_step(FltTransformRecord *record); + + int get_replicate_count() const; + void set_replicate_count(int count); + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + bool extract_transform_matrix(FltRecordReader &reader); + bool extract_replicate_count(FltRecordReader &reader); + + FltError write_transform(FltRecordWriter &writer) const; + FltError write_replicate_count(FltRecordWriter &writer) const; + +private: + bool _has_transform; + LMatrix4d _transform; + + typedef pvector Transforms; + Transforms _transform_steps; + + int _replicate_count; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltBead", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltBeadID.cxx b/pandatool/src/flt/fltBeadID.cxx new file mode 100644 index 00000000..4b6dc695 --- /dev/null +++ b/pandatool/src/flt/fltBeadID.cxx @@ -0,0 +1,124 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltBeadID.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltBeadID.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltBeadID::_type_handle; + +/** + * + */ +FltBeadID:: +FltBeadID(FltHeader *header) : FltBead(header) { +} + +/** + * Returns the id (name) of this particular bead. Each MultiGen bead will + * have a unique name. + */ +const std::string &FltBeadID:: +get_id() const { + return _id; +} + +/** + * Changes the id (name) of this particular bead. This should be a name that + * is unique to this bead. + */ +void FltBeadID:: +set_id(const std::string &id) { + _id = id; +} + +/** + * Writes a quick one-line description of the record, but not its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltBeadID:: +output(std::ostream &out) const { + out << get_type(); + if (!_id.empty()) { + out << " " << _id; + } +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltBeadID:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + _id = reader.get_iterator().get_fixed_string(8); + return true; +} + +/** + * Checks whether the given bead, which follows this bead sequentially in the + * file, is an ancillary record of this bead. If it is, extracts the relevant + * information and returns true; otherwise, leaves it alone and returns false. + */ +bool FltBeadID:: +extract_ancillary(FltRecordReader &reader) { + if (reader.get_opcode() == FO_long_id) { + DatagramIterator &di = reader.get_iterator(); + _id = di.get_fixed_string(di.get_remaining_size()); + return true; + } + + return FltBead::extract_ancillary(reader); +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltBeadID:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.update_datagram().add_fixed_string(_id.substr(0, 7), 8); + return true; +} + +/** + * Writes whatever ancillary records are required for this record. Returns + * FE_ok on success, or something else if there is some error. + */ +FltError FltBeadID:: +write_ancillary(FltRecordWriter &writer) const { + if (_id.length() > 7) { + Datagram dc; + + // Although the manual mentions nothing of this, it is essential that the + // length of the record be a multiple of 4 bytes. + dc.add_fixed_string(_id, (_id.length() + 3) & ~3); + + FltError result = writer.write_record(FO_long_id, dc); + if (result != FE_ok) { + return result; + } + } + + return FltBead::write_ancillary(writer); +} diff --git a/pandatool/src/flt/fltBeadID.h b/pandatool/src/flt/fltBeadID.h new file mode 100644 index 00000000..d7e943b2 --- /dev/null +++ b/pandatool/src/flt/fltBeadID.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltBeadID.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTBEADID_H +#define FLTBEADID_H + +#include "pandatoolbase.h" + +#include "fltBead.h" + +/** + * A base class for any of a broad family of flt beads that include an ID. + */ +class FltBeadID : public FltBead { +public: + FltBeadID(FltHeader *header); + + const std::string &get_id() const; + void set_id(const std::string &id); + + virtual void output(std::ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + std::string _id; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltBeadID", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltCurve.I b/pandatool/src/flt/fltCurve.I new file mode 100644 index 00000000..cb3d37d2 --- /dev/null +++ b/pandatool/src/flt/fltCurve.I @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltCurve.I + * @author drose + * @date 2001-02-28 + */ + +/** + * Returns the number of control points assigned to the curve. + */ +INLINE int FltCurve:: +get_num_control_points() const { + return _control_points.size(); +} + +/** + * Returns the nth control point assigned to the curve. + */ +INLINE const LPoint3d &FltCurve:: +get_control_point(int n) const { +#ifndef NDEBUG + static LPoint3d bogus(0.0, 0.0, 0.0); + nassertr(n >= 0 && n < (int)_control_points.size(), bogus); +#endif + return _control_points[n]; +} diff --git a/pandatool/src/flt/fltCurve.cxx b/pandatool/src/flt/fltCurve.cxx new file mode 100644 index 00000000..d9b3ae8b --- /dev/null +++ b/pandatool/src/flt/fltCurve.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltCurve.cxx + * @author drose + * @date 2001-02-28 + */ + +#include "fltCurve.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltCurve::_type_handle; + +/** + * + */ +FltCurve:: +FltCurve(FltHeader *header) : FltBeadID(header) { + _curve_type = CT_b_spline; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltCurve:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_curve, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + _curve_type = (CurveType)iterator.get_be_int32(); + + int num_control_points = iterator.get_be_int32(); + iterator.skip_bytes(8); + for (int i = 0; i < num_control_points; i++) { + double x = iterator.get_be_float64(); + double y = iterator.get_be_float64(); + double z = iterator.get_be_float64(); + _control_points.push_back(LPoint3d(x, y, z)); + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltCurve:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_curve); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); + datagram.add_be_int32(_curve_type); + datagram.add_be_int32(_control_points.size()); + datagram.pad_bytes(8); + + ControlPoints::const_iterator ci; + for (ci = _control_points.begin(); ci != _control_points.end(); ++ci) { + const LPoint3d &p = (*ci); + datagram.add_be_float64(p[0]); + datagram.add_be_float64(p[1]); + datagram.add_be_float64(p[2]); + } + + return true; +} diff --git a/pandatool/src/flt/fltCurve.h b/pandatool/src/flt/fltCurve.h new file mode 100644 index 00000000..79f19daf --- /dev/null +++ b/pandatool/src/flt/fltCurve.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltCurve.h + * @author drose + * @date 2001-02-28 + */ + +#ifndef FLTCURVE_H +#define FLTCURVE_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" +#include "fltHeader.h" + +#include "luse.h" + +/** + * A single curve, like a Bezier or B-Spline. + */ +class FltCurve : public FltBeadID { +public: + FltCurve(FltHeader *header); + + enum CurveType { + CT_b_spline = 4, + CT_cardinal = 5, + CT_bezier = 6 + }; + + typedef pvector ControlPoints; + + CurveType _curve_type; + ControlPoints _control_points; + +public: + INLINE int get_num_control_points() const; + INLINE const LPoint3d &get_control_point(int n) const; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltCurve", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltCurve.I" + +#endif diff --git a/pandatool/src/flt/fltError.cxx b/pandatool/src/flt/fltError.cxx new file mode 100644 index 00000000..b0b58ea6 --- /dev/null +++ b/pandatool/src/flt/fltError.cxx @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltError.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltError.h" + +std::ostream & +operator << (std::ostream &out, FltError error) { + switch (error) { + case FE_ok: + return out << "no error"; + + case FE_could_not_open: + return out << "could not open file"; + + case FE_empty_file: + return out << "empty file"; + + case FE_end_of_file: + return out << "unexpected end of file"; + + case FE_read_error: + return out << "read error on file"; + + case FE_invalid_record: + return out << "invalid record"; + + case FE_extra_data: + return out << "extra data at end of file"; + + case FE_write_error: + return out << "write error on file"; + + case FE_bad_data: + return out << "bad data"; + + case FE_not_implemented: + return out << "not implemented"; + + case FE_internal: + return out << "internal error"; + + default: + return out << "unknown error " << (int)error; + } +} diff --git a/pandatool/src/flt/fltError.h b/pandatool/src/flt/fltError.h new file mode 100644 index 00000000..29c44538 --- /dev/null +++ b/pandatool/src/flt/fltError.h @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltError.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTERROR_H +#define FLTERROR_H + +#include "pandatoolbase.h" + +// Return values for various functions in the flt library. +enum FltError { + FE_ok = 0, + FE_could_not_open, + FE_empty_file, + FE_end_of_file, + FE_read_error, + FE_invalid_record, + FE_extra_data, + FE_write_error, + FE_bad_data, + FE_not_implemented, + FE_undefined_instance, + FE_internal +}; + +std::ostream &operator << (std::ostream &out, FltError error); + +#endif diff --git a/pandatool/src/flt/fltExternalReference.cxx b/pandatool/src/flt/fltExternalReference.cxx new file mode 100644 index 00000000..fa6aaa05 --- /dev/null +++ b/pandatool/src/flt/fltExternalReference.cxx @@ -0,0 +1,136 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltExternalReference.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltExternalReference.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "pathReplace.h" + +TypeHandle FltExternalReference::_type_handle; + +/** + * + */ +FltExternalReference:: +FltExternalReference(FltHeader *header) : FltBead(header) { + _flags = 0; +} + +/** + * Walks the hierarchy at this record and below and copies the + * _converted_filename record into the _orig_filename record, so the flt file + * will be written out with the converted filename instead of what was + * originally read in. + */ +void FltExternalReference:: +apply_converted_filenames() { + _orig_filename = _converted_filename.to_os_generic(); + FltBead::apply_converted_filenames(); +} + +/** + * Writes a quick one-line description of the record, but not its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltExternalReference:: +output(std::ostream &out) const { + out << "External " << get_ref_filename(); + if (!_bead_id.empty()) { + out << " (" << _bead_id << ")"; + } +} + +/** + * Returns the name of the referenced file. + */ +Filename FltExternalReference:: +get_ref_filename() const { + return _converted_filename; +} + +/** + * Changes the name of the referenced file. + */ +void FltExternalReference:: +set_ref_filename(const Filename &filename) { + _converted_filename = filename; + _orig_filename = _converted_filename.to_os_generic(); +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltExternalReference:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_external_ref, false); + DatagramIterator &iterator = reader.get_iterator(); + + std::string name = iterator.get_fixed_string(200); + iterator.skip_bytes(1 + 1); + iterator.skip_bytes(2); // Undocumented additional padding. + _flags = iterator.get_be_uint32(); + iterator.skip_bytes(2); + iterator.skip_bytes(2); // Undocumented additional padding. + + _orig_filename = name; + + if (!name.empty() && name[name.length() - 1] == '>') { + // Extract out the bead name. + size_t open = name.rfind('<'); + if (open != std::string::npos) { + _orig_filename = name.substr(0, open); + _bead_id = name.substr(open + 1, name.length() - open - 2); + } + } + _converted_filename = _header->convert_path(Filename::from_os_specific(_orig_filename)); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltExternalReference:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_external_ref); + Datagram &datagram = writer.update_datagram(); + + std::string name = _orig_filename; + if (!_bead_id.empty()) { + name += "<" + _bead_id + ">"; + } + + datagram.add_fixed_string(name.substr(0, 199), 200); + datagram.pad_bytes(1 + 1); + datagram.pad_bytes(2); // Undocumented additional padding. + datagram.add_be_uint32(_flags); + datagram.pad_bytes(2); + datagram.pad_bytes(2); // Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltExternalReference.h b/pandatool/src/flt/fltExternalReference.h new file mode 100644 index 00000000..03cac968 --- /dev/null +++ b/pandatool/src/flt/fltExternalReference.h @@ -0,0 +1,73 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltExternalReference.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTEXTERNALREFERENCE_H +#define FLTEXTERNALREFERENCE_H + +#include "pandatoolbase.h" + +#include "fltBead.h" + +#include "filename.h" + +/** + * An external reference to another flt file (possibly to a specific bead + * within the flt file). + */ +class FltExternalReference : public FltBead { +public: + FltExternalReference(FltHeader *header); + + virtual void apply_converted_filenames(); + virtual void output(std::ostream &out) const; + + enum Flags { + F_color_palette_override = 0x80000000, + F_material_palette_override = 0x40000000, + F_texture_palette_override = 0x20000000, + F_line_style_palette_override = 0x10000000, + F_sound_palette_override = 0x08000000, + F_light_palette_override = 0x04000000 + }; + + std::string _orig_filename; + Filename _converted_filename; + std::string _bead_id; + int _flags; + + Filename get_ref_filename() const; + void set_ref_filename(const Filename &filename); + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltExternalReference", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltEyepoint.cxx b/pandatool/src/flt/fltEyepoint.cxx new file mode 100644 index 00000000..068fc4ee --- /dev/null +++ b/pandatool/src/flt/fltEyepoint.cxx @@ -0,0 +1,135 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltEyepoint.cxx + * @author drose + * @date 2000-08-26 + */ + +#include "fltEyepoint.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +/** + * + */ +FltEyepoint:: +FltEyepoint() { + _rotation_center.set(0.0, 0.0, 0.0); + _hpr.set(0.0, 0.0, 0.0); + _rotation = LMatrix4::ident_mat(); + _fov = 60.0; + _scale = 1.0; + _near_clip = 0.1; + _far_clip = 10000.0; + _fly_through = LMatrix4::ident_mat(); + _eyepoint.set(0.0, 0.0, 0.0); + _fly_through_yaw = 0.0; + _fly_through_pitch = 0.0; + _eyepoint_direction.set(0.0, 1.0, 0.0); + _no_fly_through = true; + _ortho_mode = false; + _is_valid = true; + _image_offset_x = 0; + _image_offset_y = 0; + _image_zoom = 1; +} + +/** + * + */ +bool FltEyepoint:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _rotation_center[0] = iterator.get_be_float64(); + _rotation_center[1] = iterator.get_be_float64(); + _rotation_center[2] = iterator.get_be_float64(); + _hpr[0] = iterator.get_be_float32(); + _hpr[1] = iterator.get_be_float32(); + _hpr[2] = iterator.get_be_float32(); + int r; + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _rotation(r, c) = iterator.get_be_float32(); + } + } + _fov = iterator.get_be_float32(); + _scale = iterator.get_be_float32(); + _near_clip = iterator.get_be_float32(); + _far_clip = iterator.get_be_float32(); + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _fly_through(r, c) = iterator.get_be_float32(); + } + } + _eyepoint[0] = iterator.get_be_float32(); + _eyepoint[1] = iterator.get_be_float32(); + _eyepoint[2] = iterator.get_be_float32(); + _fly_through_yaw = iterator.get_be_float32(); + _fly_through_pitch = iterator.get_be_float32(); + _eyepoint_direction[0] = iterator.get_be_float32(); + _eyepoint_direction[1] = iterator.get_be_float32(); + _eyepoint_direction[2] = iterator.get_be_float32(); + _no_fly_through = (iterator.get_be_int32() != 0); + _ortho_mode = (iterator.get_be_int32() != 0); + _is_valid = (iterator.get_be_int32() != 0); + _image_offset_x = iterator.get_be_int32(); + _image_offset_y = iterator.get_be_int32(); + _image_zoom = iterator.get_be_int32(); + iterator.skip_bytes(4*9); + + return true; +} + +/** + * + */ +bool FltEyepoint:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_float64(_rotation_center[0]); + datagram.add_be_float64(_rotation_center[1]); + datagram.add_be_float64(_rotation_center[2]); + datagram.add_be_float32(_hpr[0]); + datagram.add_be_float32(_hpr[1]); + datagram.add_be_float32(_hpr[2]); + int r; + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_rotation(r, c)); + } + } + datagram.add_be_float32(_fov); + datagram.add_be_float32(_scale); + datagram.add_be_float32(_near_clip); + datagram.add_be_float32(_far_clip); + for (r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_fly_through(r, c)); + } + } + datagram.add_be_float32(_eyepoint[0]); + datagram.add_be_float32(_eyepoint[1]); + datagram.add_be_float32(_eyepoint[2]); + datagram.add_be_float32(_fly_through_yaw); + datagram.add_be_float32(_fly_through_pitch); + datagram.add_be_float32(_eyepoint_direction[0]); + datagram.add_be_float32(_eyepoint_direction[1]); + datagram.add_be_float32(_eyepoint_direction[2]); + datagram.add_be_int32(_no_fly_through); + datagram.add_be_int32(_ortho_mode); + datagram.add_be_int32(_is_valid); + datagram.add_be_int32(_image_offset_x); + datagram.add_be_int32(_image_offset_y); + datagram.add_be_int32(_image_zoom); + datagram.pad_bytes(4*9); + + return true; +} diff --git a/pandatool/src/flt/fltEyepoint.h b/pandatool/src/flt/fltEyepoint.h new file mode 100644 index 00000000..bef59fee --- /dev/null +++ b/pandatool/src/flt/fltEyepoint.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltEyepoint.h + * @author drose + * @date 2000-08-26 + */ + +#ifndef FLTEYEPOINT_H +#define FLTEYEPOINT_H + +#include "pandatoolbase.h" + +#include "luse.h" + +class FltRecordReader; +class FltRecordWriter; + +/** + * A single eyepoint entry in the eyepoint/trackplane palette. + */ +class FltEyepoint { +public: + FltEyepoint(); + + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + LPoint3d _rotation_center; + LVecBase3 _hpr; + LMatrix4 _rotation; + PN_stdfloat _fov; + PN_stdfloat _scale; + PN_stdfloat _near_clip; + PN_stdfloat _far_clip; + LMatrix4 _fly_through; + LPoint3 _eyepoint; + PN_stdfloat _fly_through_yaw; + PN_stdfloat _fly_through_pitch; + LVector3 _eyepoint_direction; + bool _no_fly_through; + bool _ortho_mode; + bool _is_valid; + int _image_offset_x; + int _image_offset_y; + int _image_zoom; +}; + +#endif diff --git a/pandatool/src/flt/fltFace.I b/pandatool/src/flt/fltFace.I new file mode 100644 index 00000000..934927d7 --- /dev/null +++ b/pandatool/src/flt/fltFace.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltFace.I + * @author drose + * @date 2000-08-30 + */ diff --git a/pandatool/src/flt/fltFace.cxx b/pandatool/src/flt/fltFace.cxx new file mode 100644 index 00000000..8770d3b6 --- /dev/null +++ b/pandatool/src/flt/fltFace.cxx @@ -0,0 +1,67 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltFace.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltFace.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltFace::_type_handle; + +/** + * + */ +FltFace:: +FltFace(FltHeader *header) : FltGeometry(header) { +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltFace:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + if (!FltGeometry::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_face, false); + + DatagramIterator &iterator = reader.get_iterator(); + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltFace:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + if (!FltGeometry::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_face); + + return true; +} diff --git a/pandatool/src/flt/fltFace.h b/pandatool/src/flt/fltFace.h new file mode 100644 index 00000000..404a4966 --- /dev/null +++ b/pandatool/src/flt/fltFace.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltFace.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTFACE_H +#define FLTFACE_H + +#include "pandatoolbase.h" + +#include "fltGeometry.h" + +/** + * A single face bead, e.g. a polygon. + */ +class FltFace : public FltGeometry { +public: + FltFace(FltHeader *header); + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltGeometry::init_type(); + register_type(_type_handle, "FltFace", + FltGeometry::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltFace.I" + +#endif diff --git a/pandatool/src/flt/fltGeometry.I b/pandatool/src/flt/fltGeometry.I new file mode 100644 index 00000000..c584c520 --- /dev/null +++ b/pandatool/src/flt/fltGeometry.I @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltGeometry.I + * @author drose + * @date 2001-02-28 + */ + +/** + * Returns true if the face has a texture applied, false otherwise. + */ +INLINE bool FltGeometry:: +has_texture() const { + return (_texture_index >= 0 && _header->has_texture(_texture_index)); +} + +/** + * Returns the texture applied to this face, or NULL if no texture was + * applied. + */ +INLINE FltTexture *FltGeometry:: +get_texture() const { + return _header->get_texture(_texture_index); +} + +/** + * Applies the indicated texture to this face, or if the texture is NULL, + * clears it. + */ +INLINE void FltGeometry:: +set_texture(FltTexture *texture) { + if (texture == nullptr) { + _texture_index = -1; + } else { + _header->add_texture(texture); + _texture_index = texture->_pattern_index; + } +} + +/** + * Returns true if the face has a material applied, false otherwise. + */ +INLINE bool FltGeometry:: +has_material() const { + return (_material_index >= 0 && _header->has_material(_material_index)); +} + +/** + * Returns the material applied to this face, or NULL if no material was + * applied. + */ +INLINE FltMaterial *FltGeometry:: +get_material() const { + return _header->get_material(_material_index); +} + +/** + * Applies the indicated material to this face, or if the material is NULL, + * clears it. + */ +INLINE void FltGeometry:: +set_material(FltMaterial *material) { + if (material == nullptr) { + _material_index = -1; + } else { + _header->add_material(material); + _material_index = material->_material_index; + } +} + +/** + * Returns true if the face has a primary color indicated, false otherwise. + */ +INLINE bool FltGeometry:: +has_color() const { + // Even if the no_color bit is not set, if the color_index is -1, the face + // doesn't have a color (unless we've got packed color). On the other hand, + // if we have a material than we always have color. + return ((_flags & F_no_color) == 0 && + (_color_index != -1 || ((_flags & F_packed_color) != 0))) + || has_material(); +} diff --git a/pandatool/src/flt/fltGeometry.cxx b/pandatool/src/flt/fltGeometry.cxx new file mode 100644 index 00000000..25820487 --- /dev/null +++ b/pandatool/src/flt/fltGeometry.cxx @@ -0,0 +1,262 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltGeometry.cxx + * @author drose + * @date 2001-02-28 + */ + +#include "fltGeometry.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltGeometry::_type_handle; + +/** + * + */ +FltGeometry:: +FltGeometry(FltHeader *header) : FltBeadID(header) { + _ir_color = 0; + _relative_priority = 0; + _draw_type = DT_solid_cull_backface; + _texwhite = false; + _color_name_index = 0; + _alt_color_name_index = 0; + _billboard_type = BT_none; + _detail_texture_index = -1; + _texture_index = -1; + _material_index = -1; + _dfad_material_code = 0; + _dfad_feature_id = 0; + _ir_material_code = 0; + _transparency = 0; + _lod_generation_control = 0; + _line_style_index = 0; + _flags = F_no_color; + _light_mode = LM_face_no_normal; + _texture_mapping_index = 0; + _color_index = 0; + _alt_color_index = 0; +} + + +/** + * Returns the primary color of the face, as a four-component value (including + * alpha as the transparency channel). + * + * If has_color() is false, the result is white, but still reflects the + * transparency correctly. + */ +LColor FltGeometry:: +get_color() const { + LColor color; + + if (!has_color() || (_texwhite && has_texture())) { + // Force this one white. + color.set(1.0, 1.0, 1.0, 1.0); + + } else if (has_material()) { + // If we have a material, that replaces the color. + FltMaterial *material = get_material(); + color.set(material->_diffuse[0], + material->_diffuse[1], + material->_diffuse[2], + material->_alpha); + } else { + LRGBColor rgb = + _header->get_rgb(_color_index, (_flags & F_packed_color) != 0, + _packed_color); + color.set(rgb[0], rgb[1], rgb[2], 1.0); + } + + // Modify the whole thing by our transparency. + PN_stdfloat alpha = 1.0 - (_transparency / 65535.0); + color[3] *= alpha; + + return color; +} + +/** + * Sets the primary color of the face, using the packed color convention. + */ +void FltGeometry:: +set_color(const LColor &color) { + set_rgb(LRGBColor(color[0], color[1], color[2])); + _transparency = (int)floor((1.0 - color[3]) * 65535.0); +} + +/** + * Returns the primary color of the face, as a three-component value ignoring + * transparency. + */ +LRGBColor FltGeometry:: +get_rgb() const { + if (!has_color() || (_texwhite && has_texture())) { + // Force this one white. + return LRGBColor(1.0, 1.0, 1.0); + } + + if (has_material()) { + // If we have a material, that replaces the color. + FltMaterial *material = get_material(); + return material->_diffuse; + } + + return _header->get_rgb(_color_index, (_flags & F_packed_color) != 0, + _packed_color); +} + +/** + * Sets the primary color of the face, using the packed color convention; does + * not affect transparency. + */ +void FltGeometry:: +set_rgb(const LRGBColor &rgb) { + _packed_color.set_rgb(rgb); + _flags = ((_flags & ~F_no_color) | F_packed_color); + + // If we have a color, we can't have a material. + _material_index = -1; + _texwhite = false; +} + +/** + * Returns true if the face has an alternate color indicated, false otherwise. + */ +bool FltGeometry:: +has_alt_color() const { + return (_flags & F_no_alt_color) == 0; +} + +/** + * If has_alt_color() indicates true, returns the alternate color of the face, + * as a four-component value (including alpha as the transparency channel). + */ +LColor FltGeometry:: +get_alt_color() const { + nassertr(has_alt_color(), LColor(0.0, 0.0, 0.0, 0.0)); + + return _header->get_color(_alt_color_index, (_flags & F_packed_color) != 0, + _alt_packed_color, _transparency); +} + +/** + * If has_alt_color() indicates true, returns the alternate color of the face, + * as a three-component value ignoring transparency. + */ +LRGBColor FltGeometry:: +get_alt_rgb() const { + nassertr(has_alt_color(), LRGBColor(0.0, 0.0, 0.0)); + + return _header->get_rgb(_alt_color_index, (_flags & F_packed_color) != 0, + _alt_packed_color); +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltGeometry:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _ir_color = iterator.get_be_int32(); + _relative_priority = iterator.get_be_int16(); + _draw_type = (DrawType)iterator.get_int8(); + _texwhite = (iterator.get_int8() != 0); + _color_name_index = iterator.get_be_int16(); + _alt_color_name_index = iterator.get_be_int16(); + iterator.skip_bytes(1); + _billboard_type = (BillboardType)iterator.get_int8(); + _detail_texture_index = iterator.get_be_int16(); + _texture_index = iterator.get_be_int16(); + _material_index = iterator.get_be_int16(); + _dfad_material_code = iterator.get_be_int16(); + _dfad_feature_id = iterator.get_be_int16(); + _ir_material_code = iterator.get_be_int32(); + _transparency = iterator.get_be_uint16(); + _lod_generation_control = iterator.get_uint8(); + _line_style_index = iterator.get_uint8(); + if (_header->get_flt_version() >= 1420) { + _flags = iterator.get_be_uint32(); + _light_mode = (LightMode)iterator.get_uint8(); + iterator.skip_bytes(1 + 4); + iterator.skip_bytes(2); // Undocumented padding. + + if (!_packed_color.extract_record(reader)) { + return false; + } + if (!_alt_packed_color.extract_record(reader)) { + return false; + } + + if (_header->get_flt_version() >= 1520) { + _texture_mapping_index = iterator.get_be_int16(); + iterator.skip_bytes(2); + _color_index = iterator.get_be_int32(); + _alt_color_index = iterator.get_be_int32(); + iterator.skip_bytes(2 + 2); + } + } + + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltGeometry:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_ir_color); + datagram.add_be_int16(_relative_priority); + datagram.add_int8(_draw_type); + datagram.add_int8(_texwhite); + datagram.add_be_uint16(_color_name_index); + datagram.add_be_uint16(_alt_color_name_index); + datagram.pad_bytes(1); + datagram.add_int8(_billboard_type); + datagram.add_be_int16(_detail_texture_index); + datagram.add_be_int16(_texture_index); + datagram.add_be_int16(_material_index); + datagram.add_be_int16(_dfad_material_code); + datagram.add_be_int16(_dfad_feature_id); + datagram.add_be_int32(_ir_material_code); + datagram.add_be_uint16(_transparency); + datagram.add_uint8(_lod_generation_control); + datagram.add_uint8(_line_style_index); + datagram.add_be_uint32(_flags); + datagram.add_uint8(_light_mode); + datagram.pad_bytes(1 + 4); + datagram.pad_bytes(2); // Undocumented padding. + + if (!_packed_color.build_record(writer)) { + return false; + } + if (!_alt_packed_color.build_record(writer)) { + return false; + } + + if (_header->get_flt_version() >= 1520) { + // New with 15.2 + datagram.add_be_int16(_texture_mapping_index); + datagram.pad_bytes(2); + datagram.add_be_int32(_color_index); + datagram.add_be_int32(_alt_color_index); + datagram.pad_bytes(2 + 2); + } + + return true; +} diff --git a/pandatool/src/flt/fltGeometry.h b/pandatool/src/flt/fltGeometry.h new file mode 100644 index 00000000..f8041f10 --- /dev/null +++ b/pandatool/src/flt/fltGeometry.h @@ -0,0 +1,139 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltGeometry.h + * @author drose + * @date 2001-02-28 + */ + +#ifndef FLTGEOMETRY_H +#define FLTGEOMETRY_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" +#include "fltPackedColor.h" +#include "fltHeader.h" + +#include "luse.h" + +class FltTexture; +class FltMaterial; + +/** + * This is a base class for both FltFace and FltMesh, which are two different + * kinds of geometric primitives that might be encountered in a MultiGen file. + * They have similar properties. + */ +class FltGeometry : public FltBeadID { +public: + FltGeometry(FltHeader *header); + + enum DrawType { + DT_solid_cull_backface = 0, + DT_solid_no_cull = 1, + DT_wireframe = 2, + DT_wireframe_close = 3, + DT_wireframe_highlight = 4, + DT_omni_light = 8, + DT_uni_light = 9, + DT_bi_light = 10 + }; + + enum BillboardType { + BT_none = 0, + BT_fixed = 1, + BT_axial = 2, + BT_point = 4 + }; + + enum Flags { + F_terrain = 0x80000000, + F_no_color = 0x40000000, + F_no_alt_color = 0x20000000, + F_packed_color = 0x10000000, + F_terrain_footprint = 0x08000000, + F_hidden = 0x04000000 + }; + + enum LightMode { + LM_face_no_normal = 0, + LM_vertex_no_normal = 1, + LM_face_with_normal = 2, + LM_vertex_with_normal = 3 + }; + + int _ir_color; + int _relative_priority; + DrawType _draw_type; + bool _texwhite; + int _color_name_index; + int _alt_color_name_index; + BillboardType _billboard_type; + int _detail_texture_index; + int _texture_index; + int _material_index; + int _dfad_material_code; + int _dfad_feature_id; + int _ir_material_code; + int _transparency; + int _lod_generation_control; + int _line_style_index; + unsigned int _flags; + LightMode _light_mode; + FltPackedColor _packed_color; + FltPackedColor _alt_packed_color; + int _texture_mapping_index; + int _color_index; + int _alt_color_index; + +public: + INLINE bool has_texture() const; + INLINE FltTexture *get_texture() const; + INLINE void set_texture(FltTexture *texture); + + INLINE bool has_material() const; + INLINE FltMaterial *get_material() const; + INLINE void set_material(FltMaterial *material); + + INLINE bool has_color() const; + LColor get_color() const; + void set_color(const LColor &color); + LRGBColor get_rgb() const; + void set_rgb(const LRGBColor &rgb); + + bool has_alt_color() const; + LColor get_alt_color() const; + LRGBColor get_alt_rgb() const; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltGeometry", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltGeometry.I" + +#endif diff --git a/pandatool/src/flt/fltGroup.cxx b/pandatool/src/flt/fltGroup.cxx new file mode 100644 index 00000000..c194769f --- /dev/null +++ b/pandatool/src/flt/fltGroup.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltGroup.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltGroup.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltGroup::_type_handle; + +/** + * + */ +FltGroup:: +FltGroup(FltHeader *header) : FltBeadID(header) { + _relative_priority = 0; + _flags = 0; + _special_id1 = 0; + _special_id2 = 0; + _significance = 0; + _layer_id = 0; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltGroup:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_group, false); + DatagramIterator &iterator = reader.get_iterator(); + + _relative_priority = iterator.get_be_int16(); + iterator.skip_bytes(2); + _flags = iterator.get_be_uint32(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _significance = iterator.get_be_int16(); + _layer_id = iterator.get_int8(); + iterator.skip_bytes(1); + if (_header->get_flt_version() >= 1420) { + iterator.skip_bytes(4); + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltGroup:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_group); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_relative_priority); + datagram.pad_bytes(2); + datagram.add_be_uint32(_flags); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_int16(_significance); + datagram.add_int8(_layer_id); + datagram.pad_bytes(5); + + return true; +} diff --git a/pandatool/src/flt/fltGroup.h b/pandatool/src/flt/fltGroup.h new file mode 100644 index 00000000..c1f38198 --- /dev/null +++ b/pandatool/src/flt/fltGroup.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltGroup.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTGROUP_H +#define FLTGROUP_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" + +/** + * The main grouping bead of the flt file. + */ +class FltGroup : public FltBeadID { +public: + FltGroup(FltHeader *header); + + enum Flags { + F_forward_animation = 0x40000000, + F_swing_animation = 0x20000000, + F_bounding_box = 0x10000000, + F_freeze_bounding_box = 0x08000000, + F_default_parent = 0x04000000, + }; + + int _relative_priority; + unsigned int _flags; + int _special_id1, _special_id2; + int _significance; + int _layer_id; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltGroup", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltHeader.cxx b/pandatool/src/flt/fltHeader.cxx new file mode 100644 index 00000000..a0874863 --- /dev/null +++ b/pandatool/src/flt/fltHeader.cxx @@ -0,0 +1,1703 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltHeader.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltHeader.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltUnsupportedRecord.h" +#include "config_flt.h" +#include "zStream.h" +#include "nearly_zero.h" +#include "virtualFileSystem.h" + +#include +#include + +TypeHandle FltHeader::_type_handle; + +/** + * The FltHeader constructor accepts a PathReplace pointer; it uses this + * object to automatically convert all external filename and texture + * references. (This is necessary because the FltHeader has to look in the + * same directory as the texture to find the .attr file, so it must pre- + * convert at least the texture references.) + * + * Most of the other file converters do not have this requirement, so they do + * not need to pre-convert any pathname references. + */ +FltHeader:: +FltHeader(PathReplace *path_replace) : FltBeadID(this) { + if (path_replace == nullptr) { + _path_replace = new PathReplace; + _path_replace->_path_store = PS_absolute; + } else { + _path_replace = path_replace; + } + + _format_revision_level = 1570; + _edit_revision_level = 1570; + _next_group_id = 1; + _next_lod_id = 1; + _next_object_id = 1; + _next_face_id = 1; + _unit_multiplier = 1; + _vertex_units = U_feet; + _texwhite_new = false; + _flags = 0; + _projection_type = PT_flat_earth; + _next_dof_id = 1; + _vertex_storage_type = VTS_double; + _database_origin = DO_open_flight; + _sw_x = 0.0; + _sw_y = 0.0; + _delta_x = 0.0; + _delta_y = 0.0; + _next_sound_id = 1; + _next_path_id = 1; + _next_clip_id = 1; + _next_text_id = 1; + _next_bsp_id = 1; + _next_switch_id = 1; + _sw_lat = 0.0; + _sw_long = 0.0; + _ne_lat = 0.0; + _ne_long = 0.0; + _origin_lat = 0.0; + _origin_long = 0.0; + _lambert_upper_lat = 0.0; + _lambert_lower_lat = 0.0; + _next_light_id = 1; + _next_road_id = 1; + _next_cat_id = 1; + + // New with 15.2 + _earth_model = EM_wgs84; + + // New with 15.6 + _next_adaptive_id = 0; + _next_curve_id = 0; + + // New with 15.7 + _delta_z = 0.0; + _radius = 0.0; + _next_mesh_id = 0; + + _vertex_lookups_stale = false; + _current_vertex_offset = 0; + _next_material_index = 1; + _next_pattern_index = 1; + _got_color_palette = false; + _got_14_material_palette = false; + _got_eyepoint_trackplane_palette = false; + + _auto_attr_update = AU_if_missing; +} + +/** + * Walks the hierarchy at this record and below and copies the + * _converted_filename record into the _orig_filename record, so the flt file + * will be written out with the converted filename instead of what was + * originally read in. + */ +void FltHeader:: +apply_converted_filenames() { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + FltTexture *texture = (*ti).second; + texture->apply_converted_filenames(); + } + + FltBeadID::apply_converted_filenames(); +} + +/** + * Replaces the PathReplace object (which specifies how to mangle paths from + * the source to the destination file) with a new one. + */ +void FltHeader:: +set_path_replace(PathReplace *path_replace) { + _path_replace = path_replace; +} + +/** + * Returns a pointer to the PathReplace object associated with this converter. + * If the converter is non-const, this returns a non-const pointer, which can + * be adjusted. + */ +PathReplace *FltHeader:: +get_path_replace() { + return _path_replace; +} + +/** + * Returns a pointer to the PathReplace object associated with this converter. + * If the converter is non-const, this returns a non-const pointer, which can + * be adjusted. + */ +const PathReplace *FltHeader:: +get_path_replace() const { + return _path_replace; +} + +/** + * Uses the PathReplace object to convert the named filename as read from the + * flt record to its actual name. + */ +Filename FltHeader:: +convert_path(const Filename &orig_filename, const DSearchPath &additional_path) { + DSearchPath file_path; + if (!_flt_filename.empty()) { + file_path.append_directory(_flt_filename.get_dirname()); + } + file_path.append_path(additional_path); + return _path_replace->convert_path(orig_filename, file_path); +} + +/** + * Sets the filename--especially the directory part--in which the flt file is + * considered to reside. This is also implicitly set by read_flt(). + */ +void FltHeader:: +set_flt_filename(const Filename &flt_filename) { + _flt_filename = flt_filename; +} + +/** + * Returns the directory in which the flt file is considered to reside. + */ +const Filename &FltHeader:: +get_flt_filename() const { + return _flt_filename; +} + +/** + * Opens the indicated filename for reading and attempts to read the complete + * Flt file. Returns FE_ok on success, otherwise on failure. + */ +FltError FltHeader:: +read_flt(Filename filename) { + filename.set_binary(); + _flt_filename = filename; + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *in = vfs->open_read_file(filename, true); + if (in == nullptr) { + assert(!flt_error_abort); + return FE_could_not_open; + } + FltError result = read_flt(*in); + vfs->close_read_file(in); + return result; +} + +/** + * Attempts to read a complete Flt file from the already-opened stream. + * Returns FE_ok on success, otherwise on failure. + */ +FltError FltHeader:: +read_flt(std::istream &in) { + FltRecordReader reader(in); + FltError result = reader.advance(); + if (result == FE_end_of_file) { + assert(!flt_error_abort); + return FE_empty_file; + } else if (result != FE_ok) { + return result; + } + + result = read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + + if (!reader.eof()) { + assert(!flt_error_abort); + return FE_extra_data; + } + + return FE_ok; +} + + +/** + * Opens the indicated filename for writing and attempts to write the complete + * Flt file. Returns FE_ok on success, otherwise on failure. + */ +FltError FltHeader:: +write_flt(Filename filename) { + filename.set_binary(); + + std::ofstream out; + if (!filename.open_write(out)) { + assert(!flt_error_abort); + return FE_could_not_open; + } + +#ifdef HAVE_ZLIB + if (filename.get_extension() == "pz") { + // The filename ends in .pz, which means to automatically compress the flt + // file that we write. + OCompressStream compressor(&out, false); + return write_flt(compressor); + } +#endif // HAVE_ZLIB + + return write_flt(out); +} + +/** + * Attempts to write a complete Flt file to the already-opened stream. + * Returns FE_ok on success, otherwise on failure. + */ +FltError FltHeader:: +write_flt(std::ostream &out) { + FltRecordWriter writer(out); + FltError result = write_record_and_children(writer); + + if (out.fail()) { + assert(!flt_error_abort); + return FE_write_error; + } + return result; +} + +/** + * Controls whether texture .attr files are written automatically when + * write_flt() is called. There are three possibilities: + * + * AU_none: the .attr files are not written automatically; they must be + * written explicitly via a call to FltTexture::write_attr_data() if you want + * them to be written. + * + * AU_if_missing: the .attr files are written only if they do not already + * exist. This will not update any .attr files, even if the data is changed. + * + * AU_always: the .attr files are always rewritten, even if they already exist + * and even if the data has not changed. + * + * The default is AU_if_missing. + */ +void FltHeader:: +set_auto_attr_update(FltHeader::AttrUpdate attr) { + _auto_attr_update = attr; +} + +/** + * Returns the current setting of the auto_attr_update flag. See + * sett_auto_attr_update(). + */ +FltHeader::AttrUpdate FltHeader:: +get_auto_attr_update() const { + return _auto_attr_update; +} + +/** + * Returns the version number of the flt file as reported in the header, times + * 100. Divide by 100 to get the floating-point version number. + */ +int FltHeader:: +get_flt_version() const { + if (_format_revision_level < 1420) { + return _format_revision_level * 100; + } else { + return _format_revision_level; + } +} + +/** + * Changes the version number of the flt file that will be reported in the + * header. Pass in the floating-point version number times 100. + */ +void FltHeader:: +set_flt_version(int version) { + if (version < 14.2) { + _format_revision_level = version / 100; + } else { + _format_revision_level = version; + } +} + +/** + * Returns the earliest flt version number that this codebase supports (times + * 100). Earlier versions will probably not work. + */ +int FltHeader:: +min_flt_version() { + return 1400; +} + +/** + * Returns the latest flt version number that this codebase is known to + * support (times 100). Later versions might work, but then again they may + * not. + */ +int FltHeader:: +max_flt_version() { + return 1570; +} + +/** + * Verifies that the version number read from the header is an understood + * version number, and prints a warning to the user if this is not so--the + * read may or may not succeed. Returns true if the version number is + * acceptable (and no warning is printed), or false if it is questionable (and + * a warning is printed). + */ +bool FltHeader:: +check_version() const { + int version = get_flt_version(); + + if (version < min_flt_version()) { + nout << "Warning! The version number of this file appears to be " + << version / 100.0 << ", which is older than " << min_flt_version() / 100.0 + << ", the oldest OpenFlight version understood by this program. " + "It is unlikely that this program will be able to read the file " + "correctly.\n"; + return false; + } + + if (version > max_flt_version()) { + nout << "Warning! The version number of this file appears to be " + << version / 100.0 << ", which is newer than " << max_flt_version() / 100.0 + << ", the newest OpenFlight version understood by this program. " + "Chances are good that the program will still be able to read it " + "correctly, but any features in the file that are specific to " + "the latest version of OpenFlight will not be understood.\n"; + return false; + } + + return true; +} + +/** + * Returns the units indicated by the flt header, or DU_invalid if the units + * in the header are not understood. + */ +DistanceUnit FltHeader:: +get_units() const { + switch (_vertex_units) { + case FltHeader::U_meters: + return DU_meters; + + case FltHeader::U_kilometers: + return DU_kilometers; + + case FltHeader::U_feet: + return DU_feet; + + case FltHeader::U_inches: + return DU_inches; + + case FltHeader::U_nautical_miles: + return DU_nautical_miles; + } + + // Unknown units. + return DU_invalid; +} + +/** + * Returns true if a instance subtree with the given index has been defined. + */ +bool FltHeader:: +has_instance(int instance_index) const { + return (_instances.count(instance_index) != 0); +} + +/** + * Returns the instance subtree associated with the given index, or NULL if + * there is no such instance. + */ +FltInstanceDefinition *FltHeader:: +get_instance(int instance_index) const { + Instances::const_iterator mi; + mi = _instances.find(instance_index); + if (mi != _instances.end()) { + return (*mi).second; + } + return nullptr; +} + +/** + * Removes all instance subtrees from the instance pool. + */ +void FltHeader:: +clear_instances() { + _instances.clear(); +} + +/** + * Defines a new instance subtree. This subtree is not itself part of the + * hierarchy; it marks geometry that may be instanced to various beads + * elsewhere in the hierarchy by creating a corresponding FltInstanceRef bead. + */ +void FltHeader:: +add_instance(FltInstanceDefinition *instance) { + _instances[instance->_instance_index] = instance; +} + +/** + * Removes a particular instance subtree from the pool, if it exists. + */ +void FltHeader:: +remove_instance(int instance_index) { + _instances.erase(instance_index); +} + +/** + * Returns the number of vertices in the vertex palette. + */ +int FltHeader:: +get_num_vertices() const { + return _vertices.size(); +} + +/** + * Returns the nth vertex of the vertex palette. + */ +FltVertex *FltHeader:: +get_vertex(int n) const { + nassertr(n >= 0 && n < (int)_vertices.size(), nullptr); + return _vertices[n]; +} + +/** + * Removes all vertices from the vertex palette. + */ +void FltHeader:: +clear_vertices() { + _vertices.clear(); + _unique_vertices.clear(); + _vertices_by_offset.clear(); + _offsets_by_vertex.clear(); + _vertex_lookups_stale = false; +} + +/** + * Adds a new vertex to the end of the vertex palette. If this particular + * vertex was already present in the palette, does nothing. + */ +void FltHeader:: +add_vertex(FltVertex *vertex) { + bool inserted = _unique_vertices.insert(vertex).second; + if (inserted) { + _vertices.push_back(vertex); + } + _vertex_lookups_stale = true; + nassertv(_unique_vertices.size() == _vertices.size()); +} + +/** + * Returns the particular vertex pointer associated with the given byte offset + * into the vertex palette. If there is no such vertex in the palette, this + * generates an error message and returns NULL. + */ +FltVertex *FltHeader:: +get_vertex_by_offset(int offset) { + if (_vertex_lookups_stale) { + update_vertex_lookups(); + } + + VerticesByOffset::const_iterator vi; + vi = _vertices_by_offset.find(offset); + if (vi == _vertices_by_offset.end()) { + nout << "No vertex with offset " << offset << "\n"; + return nullptr; + } + return (*vi).second; +} + +/** + * Returns the byte offset into the vertex palette associated with the given + * vertex pointer. If there is no such vertex in the palette, this generates + * an error message and returns 0. + */ +int FltHeader:: +get_offset_by_vertex(FltVertex *vertex) { + if (_vertex_lookups_stale) { + update_vertex_lookups(); + } + + OffsetsByVertex::const_iterator vi; + vi = _offsets_by_vertex.find(vertex); + if (vi == _offsets_by_vertex.end()) { + nout << "Vertex does not appear in palette.\n"; + return 0; + } + return (*vi).second; +} + +/** + * Returns the total number of different colors in the color palette. This + * includes all different colors, and represents the complete range of + * alloable color indices. This is different from the actual number of color + * entries as read directly from the color palette, since each color entry + * defines a number of different intensity levels--the value returned by + * get_num_colors() is equal to get_num_color_entries() * + * get_num_color_shades(). + */ +int FltHeader:: +get_num_colors() const { + return _colors.size() * get_num_color_shades(); +} + +/** + * Returns the four-component color corresponding to the given color index. + * Each component will be in the range [0, 1]. + */ +LColor FltHeader:: +get_color(int color_index) const { + nassertr(color_index >= 0 && color_index < get_num_colors(), + LColor(0.0, 0.0, 0.0, 0.0)); + int num_color_shades = get_num_color_shades(); + + int index = (color_index / num_color_shades); + int level = (color_index % num_color_shades); + nassertr(index >= 0 && index < (int)_colors.size(), + LColor(0.0, 0.0, 0.0, 0.0)); + + LColor color = _colors[index].get_color(); + return color * ((double)level / (double)(num_color_shades - 1)); +} + +/** + * Returns the three-component color corresponding to the given color index, + * ignoring the alpha component. Each component will be in the range [0, 1]. + */ +LRGBColor FltHeader:: +get_rgb(int color_index) const { + nassertr(color_index >= 0 && color_index < get_num_colors(), + LRGBColor(0.0, 0.0, 0.0)); + int num_color_shades = get_num_color_shades(); + + int index = (color_index / num_color_shades); + int level = (color_index % num_color_shades); + nassertr(index >= 0 && index < (int)_colors.size(), + LRGBColor(0.0, 0.0, 0.0)); + + LRGBColor color = _colors[index].get_rgb(); + return color * ((double)level / (double)(num_color_shades - 1)); +} + +/** + * Returns true if the given color is named, false otherwise. + */ +bool FltHeader:: +has_color_name(int color_index) const { + return (_color_names.count(color_index) != 0); +} + +/** + * Returns the name associated with the given color, if any. + */ +std::string FltHeader:: +get_color_name(int color_index) const { + ColorNames::const_iterator ni; + ni = _color_names.find(color_index); + if (ni != _color_names.end()) { + return (*ni).second; + } + return std::string(); +} + +/** + * Returns the color index of the nearest color in the palette that matches + * the given four-component color, including alpha. + */ +int FltHeader:: +get_closest_color(const LColor &color0) const { + // Since the colortable stores the brightest colors, with num_color_shades + // scaled versions of each color implicitly available, we really only care + // about the relative brightnesses of the various components. Normalize the + // color in terms of the largest of these. + LColor color = color0; + + double scale = 1.0; + + if (color[0] == 0.0 && color[1] == 0.0 && color[2] == 0.0 && color[3] == 0.0) { + // Oh, this is invisible black. + scale = 0.0; + color.set(1.0, 1.0, 1.0, 1.0); + + } else { + if (color[0] >= color[1] && color[0] >= color[2] && color[0] >= color[3]) { + // color[0] is largest. + scale = color[0]; + + } else if (color[1] >= color[2] && color[1] >= color[3]) { + // color[1] is largest. + scale = color[1]; + + } else if (color[2] >= color[3]) { + // color[2] is largest. + scale = color[2]; + + } else { + // color[3] is largest. + scale = color[3]; + } + color /= scale; + } + + // Now search for the best match. + PN_stdfloat best_dist = 5.0; // Greater than 4. + int best_i = -1; + + int num_color_entries = get_num_color_entries(); + for (int i = 0; i < num_color_entries; i++) { + LColor consider = _colors[i].get_color(); + PN_stdfloat dist2 = dot(consider - color, consider - color); + nassertr(dist2 < 5.0, 0); + + if (dist2 < best_dist) { + best_dist = dist2; + best_i = i; + } + } + nassertr(best_i >= 0, 0); + + int num_color_shades = get_num_color_shades(); + int shade_index = (int)floor((num_color_shades-1) * scale + 0.5); + + return (best_i * num_color_shades) + shade_index; +} + +/** + * Returns the color index of the nearest color in the palette that matches + * the given three-component color, ignoring alpha. + */ +int FltHeader:: +get_closest_rgb(const LRGBColor &color0) const { + // Since the colortable stores the brightest colors, with num_color_shades + // scaled versions of each color implicitly available, we really only care + // about the relative brightnesses of the various components. Normalize the + // color in terms of the largest of these. + + LRGBColor color = color0; + double scale = 1.0; + + if (color[0] == 0.0 && color[1] == 0.0 && color[2] == 0.0) { + // Oh, this is black. + scale = 0.0; + color.set(1.0, 1.0, 1.0); + + } else { + if (color[0] >= color[1] && color[0] >= color[2]) { + // color[0] is largest. + scale = color[0]; + + } else if (color[1] >= color[2]) { + // color[1] is largest. + scale = color[1]; + + } else { + // color[2] is largest. + scale = color[2]; + } + color /= scale; + } + + // Now search for the best match. + PN_stdfloat best_dist = 5.0; // Greater than 4. + int best_i = -1; + + int num_color_entries = get_num_color_entries(); + for (int i = 0; i < num_color_entries; i++) { + LRGBColor consider = _colors[i].get_rgb(); + PN_stdfloat dist2 = dot(consider - color, consider - color); + nassertr(dist2 < 5.0, 0); + + if (dist2 < best_dist) { + best_dist = dist2; + best_i = i; + } + } + nassertr(best_i >= 0, 0); + + int num_color_shades = get_num_color_shades(); + int shade_index = (int)floor((num_color_shades-1) * scale + 0.5); + + return (best_i * num_color_shades) + shade_index; +} + +/** + * Returns the number of actual entries in the color palette. This is based + * on the version of the flt file, and is usually either 512 or 1024. + */ +int FltHeader:: +get_num_color_entries() const { + return _colors.size(); +} + +/** + * Returns the number of shades of brightness of each entry in the color + * palette. This is a fixed property of MultiGen files: each entry in the + * palette actually represents a range of this many colors. + */ +int FltHeader:: +get_num_color_shades() const { + return 128; +} + +/** + * Decodes a MultiGen color, as stored on a face or vertex, into an actual + * four-component LColor. Normally you need not call this directly; there are + * color accessors defined on faces and vertices that do this. + */ +LColor FltHeader:: +get_color(int color_index, bool use_packed_color, + const FltPackedColor &packed_color, + int transparency) { + if (!use_packed_color) { + return get_color(color_index); + } + + LColor color; + color[0] = packed_color._r / 255.0; + color[1] = packed_color._g / 255.0; + color[2] = packed_color._b / 255.0; + // MultiGen doesn't yet use the A component of RGBA. color[3] = + // packed_color._a 255.0; + color[3] = 1.0 - (transparency / 65535.0); + return color; +} + +/** + * Decodes a MultiGen color, as stored on a face or vertex, into an actual + * three-component LRGBColor. Normally you need not call this directly; there + * are color accessors defined on faces and vertices that do this. + */ +LRGBColor FltHeader:: +get_rgb(int color_index, bool use_packed_color, + const FltPackedColor &packed_color) { + if (!use_packed_color) { + return get_rgb(color_index); + } + + LRGBColor color; + color[0] = packed_color._r / 255.0; + color[1] = packed_color._g / 255.0; + color[2] = packed_color._b / 255.0; + return color; +} + +/** + * Returns true if a material with the given index has been defined. + */ +bool FltHeader:: +has_material(int material_index) const { + return (_materials.count(material_index) != 0); +} + +/** + * Returns the material associated with the given index, or NULL if there is + * no such material. + */ +FltMaterial *FltHeader:: +get_material(int material_index) const { + Materials::const_iterator mi; + mi = _materials.find(material_index); + if (mi != _materials.end()) { + return (*mi).second; + } + return nullptr; +} + +/** + * Removes all materials from the palette. + */ +void FltHeader:: +clear_materials() { + _materials.clear(); +} + +/** + * Defines a new material. The material is added in the position indicated by + * the material's index number. If there is already a material defined for + * that index number, it is replaced. + */ +void FltHeader:: +add_material(FltMaterial *material) { + if (material->_material_index < 0) { + // We need to make up a new material index for the material. + material->_material_index = _next_material_index; + _next_material_index++; + + } else { + // Make sure our next generated material index will be different from any + // existing material indices. + _next_material_index = std::max(_next_material_index, material->_material_index + 1); + } + + _materials[material->_material_index] = material; +} + +/** + * Removes a particular material from the material palette, if it exists. + */ +void FltHeader:: +remove_material(int material_index) { + _materials.erase(material_index); +} + +/** + * Returns true if a texture with the given index has been defined. + */ +bool FltHeader:: +has_texture(int texture_index) const { + return (_textures.count(texture_index) != 0); +} + +/** + * Returns the texture associated with the given index, or NULL if there is no + * such texture. + */ +FltTexture *FltHeader:: +get_texture(int texture_index) const { + Textures::const_iterator mi; + mi = _textures.find(texture_index); + if (mi != _textures.end()) { + return (*mi).second; + } + return nullptr; +} + +/** + * Removes all textures from the palette. + */ +void FltHeader:: +clear_textures() { + _textures.clear(); +} + +/** + * Defines a new texture. The texture is added in the position indicated by + * the texture's index number. If there is already a texture defined for that + * index number, it is replaced. + */ +void FltHeader:: +add_texture(FltTexture *texture) { + if (texture->_pattern_index < 0) { + // We need to make up a new pattern index for the texture. + texture->_pattern_index = _next_pattern_index; + _next_pattern_index++; + + } else { + // Make sure our next generated pattern index will be different from any + // existing texture indices. + _next_pattern_index = std::max(_next_pattern_index, texture->_pattern_index + 1); + } + + _textures[texture->_pattern_index] = texture; +} + +/** + * Removes a particular texture from the texture palette, if it exists. + */ +void FltHeader:: +remove_texture(int texture_index) { + _textures.erase(texture_index); +} + +/** + * Returns true if a light source with the given index has been defined. + */ +bool FltHeader:: +has_light_source(int light_index) const { + return (_light_sources.count(light_index) != 0); +} + +/** + * Returns the light source associated with the given index, or NULL if there + * is no such light source. + */ +FltLightSourceDefinition *FltHeader:: +get_light_source(int light_index) const { + LightSources::const_iterator li; + li = _light_sources.find(light_index); + if (li != _light_sources.end()) { + return (*li).second; + } + return nullptr; +} + +/** + * Removes all light sources from the palette. + */ +void FltHeader:: +clear_light_sources() { + _light_sources.clear(); +} + +/** + * Defines a new light source. The light source is added in the position + * indicated by its light index number. If there is already a light source + * defined for that index number, it is replaced. + */ +void FltHeader:: +add_light_source(FltLightSourceDefinition *light_source) { + _light_sources[light_source->_light_index] = light_source; +} + +/** + * Removes a particular light source from the light source palette, if it + * exists. + */ +void FltHeader:: +remove_light_source(int light_index) { + _light_sources.erase(light_index); +} + +/** + * Returns true if we have read an eyepoint/trackplane palette, and at least + * some of the eyepoints and trackplanes are therefore expected to be + * meaningful. + */ +bool FltHeader:: +got_eyepoint_trackplane_palette() const { + return _got_eyepoint_trackplane_palette; +} + +/** + * Sets the state of the eyepoint/trackplane palette flag. When this is + * false, the palette is believed to be meaningless, and will not be written; + * when it is true, the palette is believed to contain at least some + * meaningful data, and will be written. + */ +void FltHeader:: +set_eyepoint_trackplane_palette(bool flag) { + _got_eyepoint_trackplane_palette = flag; +} + +/** + * Returns the number of eyepoints in the eyepoint/trackplane palette. This + * is presently fixed at 10, according to the MultiGen specs. + */ +int FltHeader:: +get_num_eyepoints() const { + return 10; +} + +/** + * Returns the nth eyepoint in the eyepoint/trackplane palette. + */ +FltEyepoint *FltHeader:: +get_eyepoint(int n) { + nassertr(n >= 0 && n < get_num_eyepoints(), nullptr); + return &_eyepoints[n]; +} + +/** + * Returns the number of trackplanes in the eyepoint/trackplane palette. This + * is presently fixed at 10, according to the MultiGen specs. + */ +int FltHeader:: +get_num_trackplanes() const { + return 10; +} + +/** + * Returns the nth trackplane in the eyepoint/trackplane palette. + */ +FltTrackplane *FltHeader:: +get_trackplane(int n) { + nassertr(n >= 0 && n < get_num_trackplanes(), nullptr); + return &_trackplanes[n]; +} + +/** + * Recomputes the offsets_by_vertex and vertices_by_offset tables. This + * reflects the flt file as it will be written out, but not necessarily as it + * was read in. + * + * The return value is the total length of the vertex palette, including the + * header record. + */ +int FltHeader:: +update_vertex_lookups() { + // We start with the length of the vertex palette record itself. + int offset = 8; + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + + _offsets_by_vertex[vertex] = offset; + _vertices_by_offset[offset] = vertex; + offset += vertex->get_record_length(); + } + + _vertex_lookups_stale = false; + + return offset; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltHeader:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_header, false); + DatagramIterator &iterator = reader.get_iterator(); + + _format_revision_level = iterator.get_be_int32(); + _edit_revision_level = iterator.get_be_int32(); + _last_revision = iterator.get_fixed_string(32); + _next_group_id = iterator.get_be_int16(); + _next_lod_id = iterator.get_be_int16(); + _next_object_id = iterator.get_be_int16(); + _next_face_id = iterator.get_be_int16(); + _unit_multiplier = iterator.get_be_int16(); + _vertex_units = (Units)iterator.get_int8(); + _texwhite_new = (iterator.get_int8() != 0); + _flags = iterator.get_be_uint32(); + iterator.skip_bytes(24); + _projection_type = (ProjectionType)iterator.get_be_int32(); + iterator.skip_bytes(28); + _next_dof_id = iterator.get_be_int16(); + _vertex_storage_type = (VertexStorageType)iterator.get_be_int16(); + _database_origin = (DatabaseOrigin)iterator.get_be_int32(); + _sw_x = iterator.get_be_float64(); + _sw_y = iterator.get_be_float64(); + _delta_x = iterator.get_be_float64(); + _delta_y = iterator.get_be_float64(); + _next_sound_id = iterator.get_be_int16(); + _next_path_id = iterator.get_be_int16(); + iterator.skip_bytes(8); + _next_clip_id = iterator.get_be_int16(); + _next_text_id = iterator.get_be_int16(); + _next_bsp_id = iterator.get_be_int16(); + _next_switch_id = iterator.get_be_int16(); + iterator.skip_bytes(4); + _sw_lat = iterator.get_be_float64(); + _sw_long = iterator.get_be_float64(); + _ne_lat = iterator.get_be_float64(); + _ne_long = iterator.get_be_float64(); + _origin_lat = iterator.get_be_float64(); + _origin_long = iterator.get_be_float64(); + _lambert_upper_lat = iterator.get_be_float64(); + _lambert_lower_lat = iterator.get_be_float64(); + _next_light_id = iterator.get_be_int16(); + iterator.skip_bytes(2); + if (get_flt_version() >= 1420 && iterator.get_remaining_size() > 0) { + _next_road_id = iterator.get_be_int16(); + _next_cat_id = iterator.get_be_int16(); + + if (get_flt_version() >= 1520 && iterator.get_remaining_size() > 0) { + iterator.skip_bytes(2 + 2 + 2 + 2); + _earth_model = (EarthModel)iterator.get_be_int32(); + + // Undocumented padding. + iterator.skip_bytes(4); + + if (get_flt_version() >= 1560 && iterator.get_remaining_size() > 0) { + _next_adaptive_id = iterator.get_be_int16(); + _next_curve_id = iterator.get_be_int16(); + iterator.skip_bytes(4); + + if (get_flt_version() >= 1570 && iterator.get_remaining_size() > 0) { + _delta_z = iterator.get_be_float64(); + _radius = iterator.get_be_float64(); + _next_mesh_id = iterator.get_be_int16(); + iterator.skip_bytes(2); + + // Undocumented padding. + iterator.skip_bytes(4); + } + } + } + } + + check_remaining_size(iterator); + return true; +} + +/** + * Checks whether the given bead, which follows this bead sequentially in the + * file, is an ancillary record of this bead. If it is, extracts the relevant + * information and returns true; otherwise, leaves it alone and returns false. + */ +bool FltHeader:: +extract_ancillary(FltRecordReader &reader) { + switch (reader.get_opcode()) { + case FO_vertex_palette: + // We're about to begin the vertex palette! + clear_vertices(); + _current_vertex_offset = reader.get_record_length(); + return true; + + case FO_vertex_c: + case FO_vertex_cn: + case FO_vertex_cnu: + case FO_vertex_cu: + // Here's a new vertex for the palette. + return extract_vertex(reader); + + case FO_color_palette: + return extract_color_palette(reader); + + case FO_15_material: + return extract_material(reader); + + case FO_14_material_palette: + return extract_14_material_palette(reader); + + case FO_texture: + return extract_texture(reader); + + case FO_texture_map_palette: + return extract_texture_map(reader); + + case FO_light_definition: + return extract_light_source(reader); + + case FO_eyepoint_palette: + return extract_eyepoint_palette(reader); + + default: + return FltBeadID::extract_ancillary(reader); + } +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltHeader:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_header); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_format_revision_level); + datagram.add_be_int32(_edit_revision_level); + datagram.add_fixed_string(_last_revision, 32); + datagram.add_be_int16(_next_group_id); + datagram.add_be_int16(_next_lod_id); + datagram.add_be_int16(_next_object_id); + datagram.add_be_int16(_next_face_id); + datagram.add_be_int16(_unit_multiplier); + datagram.add_int8(_vertex_units); + datagram.add_int8(_texwhite_new); + datagram.add_be_uint32(_flags); + datagram.pad_bytes(24); + datagram.add_be_int32(_projection_type); + datagram.pad_bytes(28); + datagram.add_be_int16(_next_dof_id); + datagram.add_be_int16(_vertex_storage_type); + datagram.add_be_int32(_database_origin); + datagram.add_be_float64(_sw_x); + datagram.add_be_float64(_sw_y); + datagram.add_be_float64(_delta_x); + datagram.add_be_float64(_delta_y); + datagram.add_be_int16(_next_sound_id); + datagram.add_be_int16(_next_path_id); + datagram.pad_bytes(8); + datagram.add_be_int16(_next_clip_id); + datagram.add_be_int16(_next_text_id); + datagram.add_be_int16(_next_bsp_id); + datagram.add_be_int16(_next_switch_id); + datagram.pad_bytes(4); + datagram.add_be_float64(_sw_lat); + datagram.add_be_float64(_sw_long); + datagram.add_be_float64(_ne_lat); + datagram.add_be_float64(_ne_long); + datagram.add_be_float64(_origin_lat); + datagram.add_be_float64(_origin_long); + datagram.add_be_float64(_lambert_upper_lat); + datagram.add_be_float64(_lambert_lower_lat); + datagram.add_be_int16(_next_light_id); + datagram.pad_bytes(2); + datagram.add_be_int16(_next_road_id); + datagram.add_be_int16(_next_cat_id); + + if (get_flt_version() >= 1520) { + // New with 15.2 + datagram.pad_bytes(2 + 2 + 2 + 2); + datagram.add_be_int32(_earth_model); + + datagram.pad_bytes(4); + + if (get_flt_version() >= 1560) { + // New with 15.6 + datagram.add_be_int16(_next_adaptive_id); + datagram.add_be_int16(_next_curve_id); + datagram.pad_bytes(4); + + if (get_flt_version() >= 1570) { + // New with 15.7 + datagram.add_be_float64(_delta_z); + datagram.add_be_float64(_radius); + datagram.add_be_int16(_next_mesh_id); + datagram.pad_bytes(2); + datagram.pad_bytes(4); + } + } + } + + return true; +} + +/** + * Writes whatever ancillary records are required for this bead. Returns + * FE_ok on success, or something else on error. + */ +FltError FltHeader:: +write_ancillary(FltRecordWriter &writer) const { + FltError result; + + result = write_color_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_material_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_texture_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_light_source_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_eyepoint_palette(writer); + if (result != FE_ok) { + return result; + } + + result = write_vertex_palette(writer); + if (result != FE_ok) { + return result; + } + + return FltBeadID::write_ancillary(writer); +} + +/** + * Reads a single vertex ancillary record. It is assumed that all the vertex + * records will immediately follow the vertex palette record. + */ +bool FltHeader:: +extract_vertex(FltRecordReader &reader) { + FltVertex *vertex = new FltVertex(this); + if (!vertex->extract_record(reader)) { + return false; + } + _vertices.push_back(vertex); + _unique_vertices.insert(vertex); + _offsets_by_vertex[vertex] = _current_vertex_offset; + _vertices_by_offset[_current_vertex_offset] = vertex; + _current_vertex_offset += reader.get_record_length(); + + // _vertex_lookups_stale remains false. + + return true; +} + +/** + * Reads the color palette. + */ +bool FltHeader:: +extract_color_palette(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_color_palette, false); + DatagramIterator &iterator = reader.get_iterator(); + + if (_got_color_palette) { + nout << "Warning: multiple color palettes found.\n"; + } + _got_color_palette = true; + + static const int expected_color_entries = 1024; + + iterator.skip_bytes(128); + _colors.clear(); + for (int i = 0; i < expected_color_entries; i++) { + if (iterator.get_remaining_size() == 0) { + // An early end to the palette is acceptable. + return true; + } + FltPackedColor color; + if (!color.extract_record(reader)) { + return false; + } + _colors.push_back(color); + } + + // Now pull out the color names. + while (iterator.get_remaining_size() > 0) { + int entry_length = iterator.get_be_uint16(); + iterator.skip_bytes(2); + if (iterator.get_remaining_size() > 0) { + int color_index = iterator.get_be_int16(); + iterator.skip_bytes(2); + + int name_length = entry_length - 8; + nassertr(color_index >= 0 && color_index < (int)_colors.size(), false); + _color_names[color_index] = iterator.get_fixed_string(name_length); + } + } + + check_remaining_size(iterator, "color palette"); + return true; +} + +/** + * Reads a single material ancillary record. + */ +bool FltHeader:: +extract_material(FltRecordReader &reader) { + PT(FltMaterial) material = new FltMaterial(this); + if (!material->extract_record(reader)) { + return false; + } + add_material(material); + + return true; +} + +/** + * Reads the v14.2 material palette. + */ +bool FltHeader:: +extract_14_material_palette(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_14_material_palette, false); + DatagramIterator &iterator = reader.get_iterator(); + + if (_got_14_material_palette) { + nout << "Warning: multiple material palettes found.\n"; + } + _got_14_material_palette = true; + + static const int expected_material_entries = 64; + + _materials.clear(); + for (int i = 0; i < expected_material_entries; i++) { + if (iterator.get_remaining_size() == 0) { + // An early end to the palette is acceptable. + return true; + } + PT(FltMaterial) material = new FltMaterial(this); + if (!material->extract_14_record(i, iterator)) { + return false; + } + add_material(material); + } + + check_remaining_size(iterator, "material palette"); + return true; +} + +/** + * Reads a single texture ancillary record. + */ +bool FltHeader:: +extract_texture(FltRecordReader &reader) { + FltTexture *texture = new FltTexture(this); + if (!texture->extract_record(reader)) { + return false; + } + add_texture(texture); + + return true; +} + +/** + * Reads the a single texture mapping ancillary record. This describes a kind + * of texture mapping in the texture mapping palette. + */ +bool FltHeader:: +extract_texture_map(FltRecordReader &reader) { + // At the moment, we ignore this, since it's not needed for meaningful + // extraction of data: we can get this information from the UV's for a + // particular model. We just add an UnsupportedRecord for it. + FltUnsupportedRecord *rec = new FltUnsupportedRecord(this); + if (!rec->extract_record(reader)) { + return false; + } + add_ancillary(rec); + + return true; +} + +/** + * Reads a single light source ancillary record. + */ +bool FltHeader:: +extract_light_source(FltRecordReader &reader) { + FltLightSourceDefinition *light_source = new FltLightSourceDefinition(this); + if (!light_source->extract_record(reader)) { + return false; + } + add_light_source(light_source); + + return true; +} + +/** + * Reads the eyepoint/trackplane palette. + */ +bool FltHeader:: +extract_eyepoint_palette(FltRecordReader &reader) { + nassertr(reader.get_opcode() == FO_eyepoint_palette, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + + int i; + int num_eyepoints = get_num_eyepoints(); + for (i = 0; i < num_eyepoints; i++) { + if (!_eyepoints[i].extract_record(reader)) { + return false; + } + } + + int num_trackplanes = get_num_trackplanes(); + for (i = 0; i < num_trackplanes; i++) { + if (!_trackplanes[i].extract_record(reader)) { + return false; + } + } + + _got_eyepoint_trackplane_palette = true; + + if (get_flt_version() >= 1420) { + // I have no idea what bytes are supposed to be here in earlier versions + // that 14.2, but who really cares? Don't bother reporting it if there + // are too many bytes in old versions. + check_remaining_size(iterator, "eyepoint palette"); + } + return true; +} + +/** + * Writes out the vertex palette with all of its vertices. + */ +FltError FltHeader:: +write_vertex_palette(FltRecordWriter &writer) const { + FltError result; + + int vertex_palette_length = + ((FltHeader *)this)->update_vertex_lookups(); + Datagram vertex_palette; + vertex_palette.add_be_int32(vertex_palette_length); + result = writer.write_record(FO_vertex_palette, vertex_palette); + if (result != FE_ok) { + return result; + } + // Now write out each vertex in the palette. + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + vertex->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + + +/** + * Writes out the color palette. + */ +FltError FltHeader:: +write_color_palette(FltRecordWriter &writer) const { + writer.set_opcode(FO_color_palette); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(128); + + // How many colors should we write? + int num_colors = 1024; + + Colors::const_iterator ci; + for (ci = _colors.begin(); num_colors > 0 && ci != _colors.end(); ++ci) { + if (!(*ci).build_record(writer)) { + assert(!flt_error_abort); + return FE_invalid_record; + } + num_colors--; + } + + // Now we might need to pad the record to fill up the required number of + // colors. + if (num_colors > 0) { + FltPackedColor empty; + while (num_colors > 0) { + if (!empty.build_record(writer)) { + assert(!flt_error_abort); + return FE_invalid_record; + } + num_colors--; + } + } + + // Now append all the names at the end. + ColorNames::const_iterator ni; + for (ni = _color_names.begin(); ni != _color_names.end(); ++ni) { + std::string name = (*ni).second.substr(0, 80); + int entry_length = name.length() + 8; + datagram.add_be_uint16(entry_length); + datagram.pad_bytes(2); + datagram.add_be_uint16((*ni).first); + datagram.pad_bytes(2); + datagram.add_fixed_string(name, name.length()); + } + + return writer.advance(); +} + +/** + * Writes out the material palette. + */ +FltError FltHeader:: +write_material_palette(FltRecordWriter &writer) const { + FltError result; + + if (get_flt_version() >= 1520) { + // Write a version 15 material palette. + Materials::const_iterator mi; + for (mi = _materials.begin(); mi != _materials.end(); ++mi) { + FltMaterial *material = (*mi).second; + material->build_record(writer); + + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + } else { + // Write a version 14 material palette. + if (_materials.empty()) { + // No palette is OK. + return FE_ok; + } + writer.set_opcode(FO_14_material_palette); + Datagram &datagram = writer.update_datagram(); + + PT(FltMaterial) dummy_material = new FltMaterial(_header); + + Materials::const_iterator mi = _materials.lower_bound(0); + int index; + static const int expected_material_entries = 64; + for (index = 0; index < expected_material_entries; index++) { + if (mi == _materials.end() || index < (*mi).first) { + dummy_material->build_14_record(datagram); + } else { + nassertr(index == (*mi).first, FE_internal); + FltMaterial *material = (*mi).second; + material->build_14_record(datagram); + ++mi; + } + } + + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +/** + * Writes out the texture palette. + */ +FltError FltHeader:: +write_texture_palette(FltRecordWriter &writer) const { + FltError result; + + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + FltTexture *texture = (*ti).second; + texture->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +/** + * Writes out the light source palette. + */ +FltError FltHeader:: +write_light_source_palette(FltRecordWriter &writer) const { + FltError result; + + LightSources::const_iterator li; + for (li = _light_sources.begin(); li != _light_sources.end(); ++li) { + FltLightSourceDefinition *light_source = (*li).second; + light_source->build_record(writer); + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +/** + * Writes out the eyepoint/trackplane palette, if we have one. + */ +FltError FltHeader:: +write_eyepoint_palette(FltRecordWriter &writer) const { + if (!_got_eyepoint_trackplane_palette) { + return FE_ok; + } + + writer.set_opcode(FO_eyepoint_palette); + Datagram &datagram = writer.update_datagram(); + datagram.pad_bytes(4); + + int i; + int num_eyepoints = get_num_eyepoints(); + for (i = 0; i < num_eyepoints; i++) { + if (!_eyepoints[i].build_record(writer)) { + assert(!flt_error_abort); + return FE_bad_data; + } + } + + int num_trackplanes = get_num_trackplanes(); + for (i = 0; i < num_trackplanes; i++) { + if (!_trackplanes[i].build_record(writer)) { + assert(!flt_error_abort); + return FE_bad_data; + } + } + + return writer.advance(); +} diff --git a/pandatool/src/flt/fltHeader.h b/pandatool/src/flt/fltHeader.h new file mode 100644 index 00000000..620e553a --- /dev/null +++ b/pandatool/src/flt/fltHeader.h @@ -0,0 +1,340 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltHeader.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTHEADER_H +#define FLTHEADER_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" +#include "fltVertex.h" +#include "fltMaterial.h" +#include "fltTexture.h" +#include "fltLightSourceDefinition.h" +#include "fltEyepoint.h" +#include "fltTrackplane.h" +#include "fltInstanceDefinition.h" + +#include "pathReplace.h" +#include "pointerTo.h" +#include "filename.h" +#include "dSearchPath.h" +#include "distanceUnit.h" +#include "pvector.h" +#include "pset.h" +#include "pmap.h" + +/** + * This is the first bead in the file, the top of the bead hierarchy, and the + * primary interface to reading and writing a Flt file. You always read a Flt + * file by creating a header and calling read_flt(), which fills in its + * children beads automatically; you write a Flt file by creating a header, + * adding its children, and calling write_flt(). + */ +class FltHeader : public FltBeadID { +public: + FltHeader(PathReplace *path_replace); + + virtual void apply_converted_filenames(); + + void set_path_replace(PathReplace *path_replace); + PathReplace *get_path_replace(); + const PathReplace *get_path_replace() const; + Filename convert_path(const Filename &orig_filename, + const DSearchPath &additional_path = DSearchPath()); + + void set_flt_filename(const Filename &flt_filename); + const Filename &get_flt_filename() const; + + FltError read_flt(Filename filename); + FltError read_flt(std::istream &in); + FltError write_flt(Filename filename); + FltError write_flt(std::ostream &out); + + enum AttrUpdate { + AU_none, + AU_if_missing, + AU_always + }; + + void set_auto_attr_update(AttrUpdate attr); + AttrUpdate get_auto_attr_update() const; + + enum Units { + U_meters = 0, + U_kilometers = 1, + U_feet = 4, + U_inches = 5, + U_nautical_miles = 8 + }; + + enum Flags { + F_save_vertex_normals = 0x80000000 + }; + + enum ProjectionType { + PT_flat_earth = 0, + PT_trapezoidal = 1, + PT_round_earth = 2, + PT_lambert = 3, + PT_utm = 4 + }; + + enum VertexStorageType { + VTS_double = 1 + }; + + enum DatabaseOrigin { + DO_open_flight = 100, + DO_dig = 200, + DO_es_ct6 = 300, + DO_psp = 400, + DO_ge_civ = 600, + DO_es_gdf = 700, + }; + + enum EarthModel { + EM_wgs84 = 0, + EM_wgs72 = 1, + EM_bessel = 2, + EM_clarke_1866 = 3, + EM_nad27 = 4 + }; + + int _format_revision_level; + int _edit_revision_level; + std::string _last_revision; + int _next_group_id; + int _next_lod_id; + int _next_object_id; + int _next_face_id; + int _unit_multiplier; + Units _vertex_units; + bool _texwhite_new; + unsigned int _flags; + ProjectionType _projection_type; + int _next_dof_id; + VertexStorageType _vertex_storage_type; + DatabaseOrigin _database_origin; + double _sw_x, _sw_y; + double _delta_x, _delta_y; + int _next_sound_id; + int _next_path_id; + int _next_clip_id; + int _next_text_id; + int _next_bsp_id; + int _next_switch_id; + double _sw_lat, _sw_long; + double _ne_lat, _ne_long; + double _origin_lat, _origin_long; + double _lambert_upper_lat, _lambert_lower_lat; + int _next_light_id; + int _next_road_id; + int _next_cat_id; + EarthModel _earth_model; + int _next_adaptive_id; + int _next_curve_id; + double _delta_z; + double _radius; + int _next_mesh_id; + +public: + int get_flt_version() const; + void set_flt_version(int version); + static int min_flt_version(); + static int max_flt_version(); + bool check_version() const; + + DistanceUnit get_units() const; + + // Accessors into the instance pool. + bool has_instance(int instance_index) const; + FltInstanceDefinition *get_instance(int instance_index) const; + void clear_instances(); + void add_instance(FltInstanceDefinition *instance); + void remove_instance(int instance_index); + + + // Accessors into the vertex palette. + int get_num_vertices() const; + FltVertex *get_vertex(int n) const; + void clear_vertices(); + void add_vertex(FltVertex *vertex); + + FltVertex *get_vertex_by_offset(int offset); + int get_offset_by_vertex(FltVertex *vertex); + + + // Accessors into the color palette. This is read-only; why would you want + // to mess with building a new color palette? + int get_num_colors() const; + LColor get_color(int color_index) const; + LRGBColor get_rgb(int color_index) const; + bool has_color_name(int color_index) const; + std::string get_color_name(int color_index) const; + + int get_closest_color(const LColor &color) const; + int get_closest_rgb(const LRGBColor &color) const; + + int get_num_color_entries() const; + int get_num_color_shades() const; + + // These functions are mainly used behind-the-scenes to decode the strange + // forest of color options defined for faces and vertices. + LColor get_color(int color_index, bool use_packed_color, + const FltPackedColor &packed_color, + int transparency); + LRGBColor get_rgb(int color_index, bool use_packed_color, + const FltPackedColor &packed_color); + + // Accessors into the material palette. + bool has_material(int material_index) const; + FltMaterial *get_material(int material_index) const; + void clear_materials(); + void add_material(FltMaterial *material); + void remove_material(int material_index); + + + // Accessors into the texture palette. + bool has_texture(int texture_index) const; + FltTexture *get_texture(int texture_index) const; + void clear_textures(); + void add_texture(FltTexture *texture); + void remove_texture(int texture_index); + + + // Accessors into the light source palette. + bool has_light_source(int light_index) const; + FltLightSourceDefinition *get_light_source(int light_index) const; + void clear_light_sources(); + void add_light_source(FltLightSourceDefinition *light_source); + void remove_light_source(int light_index); + + + // Accessors into the eyepointtrackplane palette. + bool got_eyepoint_trackplane_palette() const; + void set_eyepoint_trackplane_palette(bool flag); + + int get_num_eyepoints() const; + FltEyepoint *get_eyepoint(int n); + int get_num_trackplanes() const; + FltTrackplane *get_trackplane(int n); + +private: + // Instance subtrees. These are standalone subtrees, which may be + // referenced by various points in the hierarchy, stored by instance ID + // number. + typedef pmap Instances; + Instances _instances; + + + // Support for the vertex palette. + int update_vertex_lookups(); + + typedef pvector Vertices; + typedef pset UniqueVertices; + + typedef pmap VerticesByOffset; + typedef pmap OffsetsByVertex; + + Vertices _vertices; + UniqueVertices _unique_vertices; + VerticesByOffset _vertices_by_offset; + OffsetsByVertex _offsets_by_vertex; + + bool _vertex_lookups_stale; + + // This is maintained while the header is being read, to map the vertices to + // their corresponding offsets in the vertex palette. + int _current_vertex_offset; + + + // Support for the color palette. + bool _got_color_palette; + typedef pvector Colors; + typedef pmap ColorNames; + Colors _colors; + ColorNames _color_names; + + + // Support for the material palette. + bool _got_14_material_palette; + typedef pmap Materials; + Materials _materials; + int _next_material_index; + + + // Support for the texture palette. + AttrUpdate _auto_attr_update; + typedef pmap Textures; + Textures _textures; + int _next_pattern_index; + + + // Support for the light source palette. + typedef pmap LightSources; + LightSources _light_sources; + + + // Support for the eyepointtrackplane palette. + bool _got_eyepoint_trackplane_palette; + FltEyepoint _eyepoints[10]; + FltTrackplane _trackplanes[10]; + + // This pointer is used to resolve references in the flt file. + PT(PathReplace) _path_replace; + Filename _flt_filename; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +private: + bool extract_vertex(FltRecordReader &reader); + bool extract_color_palette(FltRecordReader &reader); + bool extract_material(FltRecordReader &reader); + bool extract_14_material_palette(FltRecordReader &reader); + bool extract_texture(FltRecordReader &reader); + bool extract_texture_map(FltRecordReader &reader); + bool extract_light_source(FltRecordReader &reader); + bool extract_eyepoint_palette(FltRecordReader &reader); + + FltError write_vertex_palette(FltRecordWriter &writer) const; + FltError write_color_palette(FltRecordWriter &writer) const; + FltError write_material_palette(FltRecordWriter &writer) const; + FltError write_texture_palette(FltRecordWriter &writer) const; + FltError write_light_source_palette(FltRecordWriter &writer) const; + FltError write_eyepoint_palette(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltHeader", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltInstanceDefinition.cxx b/pandatool/src/flt/fltInstanceDefinition.cxx new file mode 100644 index 00000000..83cf3238 --- /dev/null +++ b/pandatool/src/flt/fltInstanceDefinition.cxx @@ -0,0 +1,67 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInstanceDefinition.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltInstanceDefinition.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltInstanceDefinition::_type_handle; + +/** + * + */ +FltInstanceDefinition:: +FltInstanceDefinition(FltHeader *header) : FltBead(header) { + _instance_index = 0; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltInstanceDefinition:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_instance, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(2); + _instance_index = iterator.get_be_int16(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltInstanceDefinition:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_instance); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(2); + datagram.add_be_int16(_instance_index); + + return true; +} diff --git a/pandatool/src/flt/fltInstanceDefinition.h b/pandatool/src/flt/fltInstanceDefinition.h new file mode 100644 index 00000000..0a17f014 --- /dev/null +++ b/pandatool/src/flt/fltInstanceDefinition.h @@ -0,0 +1,59 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInstanceDefinition.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTINSTANCEDEFINITION_H +#define FLTINSTANCEDEFINITION_H + +#include "pandatoolbase.h" + +#include "fltBead.h" + +/** + * This special kind of record marks the top node of an instance subtree. + * This subtree lives outside of the normal hierarchy, and is MultiGen's way + * of supporting instancing--each instance subtree has a unique index, which + * may be referenced in a FltInstanceRef object to make the instance appear in + * various places in the hierarchy. + */ +class FltInstanceDefinition : public FltBead { +public: + FltInstanceDefinition(FltHeader *header); + + int _instance_index; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltInstanceDefinition", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltInstanceRef; + friend class FltRecordWriter; +}; + +#endif diff --git a/pandatool/src/flt/fltInstanceRef.cxx b/pandatool/src/flt/fltInstanceRef.cxx new file mode 100644 index 00000000..f33ef3b1 --- /dev/null +++ b/pandatool/src/flt/fltInstanceRef.cxx @@ -0,0 +1,112 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInstanceRef.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltInstanceRef.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltInstanceDefinition.h" +#include "fltHeader.h" + +TypeHandle FltInstanceRef::_type_handle; + +/** + * + */ +FltInstanceRef:: +FltInstanceRef(FltHeader *header) : FltBead(header) { + _instance_index = 0; +} + +/** + * Returns the instance subtree referenced by this node, or NULL if the + * reference is invalid. + */ +FltInstanceDefinition *FltInstanceRef:: +get_instance() const { + return _header->get_instance(_instance_index); +} + +/** + * Writes a multiple-line description of the record and all of its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltInstanceRef:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) << "instance"; + FltInstanceDefinition *def = _header->get_instance(_instance_index); + if (def != nullptr) { + def->write_children(out, indent_level + 2); + indent(out, indent_level) << "}\n"; + } else { + out << "\n"; + } +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltInstanceRef:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_instance_ref, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(2); + _instance_index = iterator.get_be_int16(); + + check_remaining_size(iterator); + return true; +} + +/** + * Writes this record out to the flt file, along with all of its ancillary + * records and children records. Returns FE_ok on success, or something else + * on error. + */ +FltError FltInstanceRef:: +write_record_and_children(FltRecordWriter &writer) const { + // First, make sure our instance definition has already been written. + FltError result = writer.write_instance_def(_header, _instance_index); + if (result != FE_ok) { + return result; + } + + // Then write out our own record. + return FltBead::write_record_and_children(writer); +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltInstanceRef:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_instance_ref); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(2); + datagram.add_be_int16(_instance_index); + + return true; +} diff --git a/pandatool/src/flt/fltInstanceRef.h b/pandatool/src/flt/fltInstanceRef.h new file mode 100644 index 00000000..2f8e281c --- /dev/null +++ b/pandatool/src/flt/fltInstanceRef.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInstanceRef.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTINSTANCEREF_H +#define FLTINSTANCEREF_H + +#include "pandatoolbase.h" + +#include "fltBead.h" + +class FltInstanceDefinition; + +/** + * This bead appears in the hierarchy to refer to a FltInstanceDefinition node + * defined elsewhere. It indicates that the subtree beginning at the + * FltInstanceDefinition should be considered to be instanced here. + */ +class FltInstanceRef : public FltBead { +public: + FltInstanceRef(FltHeader *header); + + int _instance_index; + + FltInstanceDefinition *get_instance() const; + + virtual void write(std::ostream &out, int indent_level = 0) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual FltError write_record_and_children(FltRecordWriter &writer) const; + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltInstanceRef", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltLOD.cxx b/pandatool/src/flt/fltLOD.cxx new file mode 100644 index 00000000..abe655ae --- /dev/null +++ b/pandatool/src/flt/fltLOD.cxx @@ -0,0 +1,91 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLOD.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltLOD.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltLOD::_type_handle; + +/** + * + */ +FltLOD:: +FltLOD(FltHeader *header) : FltBeadID(header) { + _switch_in = 0.0; + _switch_out = 0.0; + _special_id1 = 0; + _special_id2 = 0; + _flags = 0; + _center_x = 0.0; + _center_y = 0.0; + _center_z = 0.0; + _transition_range = 0.0; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltLOD:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_lod, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + _switch_in = iterator.get_be_float64(); + _switch_out = iterator.get_be_float64(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _flags = iterator.get_be_uint32(); + _center_x = iterator.get_be_float64(); + _center_y = iterator.get_be_float64(); + _center_z = iterator.get_be_float64(); + _transition_range = iterator.get_be_float64(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltLOD:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_lod); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); + datagram.add_be_float64(_switch_in); + datagram.add_be_float64(_switch_out); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_uint32(_flags); + datagram.add_be_float64(_center_x); + datagram.add_be_float64(_center_y); + datagram.add_be_float64(_center_z); + datagram.add_be_float64(_transition_range); + + return true; +} diff --git a/pandatool/src/flt/fltLOD.h b/pandatool/src/flt/fltLOD.h new file mode 100644 index 00000000..36e5ccb6 --- /dev/null +++ b/pandatool/src/flt/fltLOD.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLOD.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTLOD_H +#define FLTLOD_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" + +/** + * A Level-of-Detail record. + */ +class FltLOD : public FltBeadID { +public: + FltLOD(FltHeader *header); + + enum Flags { + F_use_previous_slant = 0x80000000, + F_freeze_center = 0x20000000 + }; + + double _switch_in; + double _switch_out; + int _special_id1, _special_id2; + unsigned int _flags; + double _center_x; + double _center_y; + double _center_z; + double _transition_range; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltLOD", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltLightSourceDefinition.cxx b/pandatool/src/flt/fltLightSourceDefinition.cxx new file mode 100644 index 00000000..52964251 --- /dev/null +++ b/pandatool/src/flt/fltLightSourceDefinition.cxx @@ -0,0 +1,129 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLightSourceDefinition.cxx + * @author drose + * @date 2000-08-26 + */ + +#include "fltLightSourceDefinition.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltLightSourceDefinition::_type_handle; + +/** + * + */ +FltLightSourceDefinition:: +FltLightSourceDefinition(FltHeader *header) : FltRecord(header) { + _light_index = 0; + _ambient.set(0.0, 0.0, 0.0, 1.0); + _diffuse.set(1.0, 1.0, 1.0, 1.0); + _specular.set(0.0, 0.0, 0.0, 1.0); + _light_type = LT_infinite; + _exponential_dropoff = 1.0; + _cutoff_angle = 180.0; + _yaw = 0.0; + _pitch = 0.0; + _constant_coefficient = 0.0; + _linear_coefficient = 0.0; + _quadratic_coefficient = 1.0; + _modeling_light = false; +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltLightSourceDefinition:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_light_definition, false); + DatagramIterator &iterator = reader.get_iterator(); + + _light_index = iterator.get_be_int32(); + iterator.skip_bytes(2*4); + _light_name = iterator.get_fixed_string(20); + iterator.skip_bytes(4); + _ambient[0] = iterator.get_be_float32(); + _ambient[1] = iterator.get_be_float32(); + _ambient[2] = iterator.get_be_float32(); + _ambient[3] = iterator.get_be_float32(); + _diffuse[0] = iterator.get_be_float32(); + _diffuse[1] = iterator.get_be_float32(); + _diffuse[2] = iterator.get_be_float32(); + _diffuse[3] = iterator.get_be_float32(); + _specular[0] = iterator.get_be_float32(); + _specular[1] = iterator.get_be_float32(); + _specular[2] = iterator.get_be_float32(); + _specular[3] = iterator.get_be_float32(); + _light_type = (LightType)iterator.get_be_int32(); + iterator.skip_bytes(4*10); + _exponential_dropoff = iterator.get_be_float32(); + _cutoff_angle = iterator.get_be_float32(); + _yaw = iterator.get_be_float32(); + _pitch = iterator.get_be_float32(); + _constant_coefficient = iterator.get_be_float32(); + _linear_coefficient = iterator.get_be_float32(); + _quadratic_coefficient = iterator.get_be_float32(); + _modeling_light = (iterator.get_be_int32() != 0); + iterator.skip_bytes(4*19); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltLightSourceDefinition:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_light_definition); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_light_index); + datagram.pad_bytes(2*4); + datagram.add_fixed_string(_light_name, 20); + datagram.pad_bytes(4); + datagram.add_be_float32(_ambient[0]); + datagram.add_be_float32(_ambient[1]); + datagram.add_be_float32(_ambient[2]); + datagram.add_be_float32(_ambient[3]); + datagram.add_be_float32(_diffuse[0]); + datagram.add_be_float32(_diffuse[1]); + datagram.add_be_float32(_diffuse[2]); + datagram.add_be_float32(_diffuse[3]); + datagram.add_be_float32(_specular[0]); + datagram.add_be_float32(_specular[1]); + datagram.add_be_float32(_specular[2]); + datagram.add_be_float32(_specular[3]); + datagram.add_be_int32(_light_type); + datagram.pad_bytes(4*10); + datagram.add_be_float32(_exponential_dropoff); + datagram.add_be_float32(_cutoff_angle); + datagram.add_be_float32(_yaw); + datagram.add_be_float32(_pitch); + datagram.add_be_float32(_constant_coefficient); + datagram.add_be_float32(_linear_coefficient); + datagram.add_be_float32(_quadratic_coefficient); + datagram.add_be_int32(_modeling_light); + datagram.pad_bytes(4*19); + + return true; +} diff --git a/pandatool/src/flt/fltLightSourceDefinition.h b/pandatool/src/flt/fltLightSourceDefinition.h new file mode 100644 index 00000000..e96d2752 --- /dev/null +++ b/pandatool/src/flt/fltLightSourceDefinition.h @@ -0,0 +1,81 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLightSourceDefinition.h + * @author drose + * @date 2000-08-26 + */ + +#ifndef FLTLIGHTSOURCEDEFINITION_H +#define FLTLIGHTSOURCEDEFINITION_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "luse.h" + +/** + * Represents a single entry in the light source palette. This completely + * defines the color, etc. of a single light source, which may be referenced + * later by a FltLightSource bead in the hierarchy. + */ +class FltLightSourceDefinition : public FltRecord { +public: + FltLightSourceDefinition(FltHeader *header); + + enum LightType { + LT_infinite = 0, + LT_local = 1, + LT_spot = 2 + }; + + int _light_index; + std::string _light_name; + LColor _ambient; + LColor _diffuse; + LColor _specular; + LightType _light_type; + PN_stdfloat _exponential_dropoff; + PN_stdfloat _cutoff_angle; // in degrees + + // yaw and pitch only for modeling lights, which are positioned at the + // eyepoint. + PN_stdfloat _yaw; + PN_stdfloat _pitch; + + PN_stdfloat _constant_coefficient; + PN_stdfloat _linear_coefficient; + PN_stdfloat _quadratic_coefficient; + bool _modeling_light; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltLightSourceDefinition", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif diff --git a/pandatool/src/flt/fltLocalVertexPool.I b/pandatool/src/flt/fltLocalVertexPool.I new file mode 100644 index 00000000..d91696c8 --- /dev/null +++ b/pandatool/src/flt/fltLocalVertexPool.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLocalVertexPool.I + * @author drose + * @date 2001-02-28 + */ diff --git a/pandatool/src/flt/fltLocalVertexPool.cxx b/pandatool/src/flt/fltLocalVertexPool.cxx new file mode 100644 index 00000000..96082ac7 --- /dev/null +++ b/pandatool/src/flt/fltLocalVertexPool.cxx @@ -0,0 +1,234 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLocalVertexPool.cxx + * @author drose + * @date 2001-02-28 + */ + +#include "fltLocalVertexPool.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltLocalVertexPool::_type_handle; + +/** + * + */ +FltLocalVertexPool:: +FltLocalVertexPool(FltHeader *header) : FltRecord(header) { +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltLocalVertexPool:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_local_vertex_pool, false); + DatagramIterator &iterator = reader.get_iterator(); + + int num_vertices = iterator.get_be_int32(); + int attributes = iterator.get_be_int32(); + + for (int i = 0; i < num_vertices; i++) { + FltVertex *vertex = new FltVertex(_header); + _vertices.push_back(vertex); + + if ((attributes & AM_has_position) != 0) { + vertex->_pos[0] = iterator.get_be_float64(); + vertex->_pos[1] = iterator.get_be_float64(); + vertex->_pos[2] = iterator.get_be_float64(); + } + + if ((attributes & AM_has_color_index) != 0) { + vertex->_color_index = iterator.get_be_int32(); + + } else if ((attributes & AM_has_packed_color) != 0) { + if (!vertex->_packed_color.extract_record(reader)) { + return false; + } + vertex->_flags |= FltVertex::F_packed_color; + + } else { + vertex->_flags |= FltVertex::F_no_color; + } + + if ((attributes & AM_has_normal) != 0) { + vertex->_normal[0] = iterator.get_be_float32(); + vertex->_normal[1] = iterator.get_be_float32(); + vertex->_normal[2] = iterator.get_be_float32(); + vertex->_has_normal = true; + } + + if ((attributes & AM_has_base_uv) != 0) { + vertex->_uv[0] = iterator.get_be_float32(); + vertex->_uv[1] = iterator.get_be_float32(); + vertex->_has_uv = true; + } + + if ((attributes & AM_has_uv_1) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_2) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_3) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_4) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_5) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_6) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + + if ((attributes & AM_has_uv_7) != 0) { + iterator.get_be_float32(); + iterator.get_be_float32(); + } + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltLocalVertexPool:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_local_vertex_pool); + Datagram &datagram = writer.update_datagram(); + + // Determine what kind of vertices we have. + int attributes = AM_has_position; + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + if ((vertex->_flags & FltVertex::F_no_color) != 0) { + // No color. + + } else if ((vertex->_flags & FltVertex::F_packed_color) != 0) { + // Packed color. + attributes |= AM_has_packed_color; + + } else { + // Indexed color. + attributes |= AM_has_color_index; + } + + if (vertex->_has_normal) { + attributes |= AM_has_normal; + } + + if (vertex->_has_uv) { + attributes |= AM_has_base_uv; + } + } + + if ((attributes & AM_has_packed_color) != 0 && + (attributes & AM_has_color_index) != 0) { + // We cannot have both a packed color and a color index. If we want both, + // used packed color. + attributes &= ~AM_has_color_index; + } + + datagram.add_be_int32(_vertices.size()); + datagram.add_be_int32(attributes); + + // Now write out each vertex. + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + FltVertex *vertex = (*vi); + + if ((attributes & AM_has_position) != 0) { + datagram.add_be_float64(vertex->_pos[0]); + datagram.add_be_float64(vertex->_pos[1]); + datagram.add_be_float64(vertex->_pos[2]); + } + + if ((attributes & AM_has_color_index) != 0) { + if ((vertex->_flags & (FltVertex::F_no_color | FltVertex::F_packed_color)) != 0) { + // This particular vertex does not have a color index. Make it white. + datagram.add_be_int32(_header->get_closest_rgb(LRGBColor(1.0, 1.0, 1.0))); + } else { + datagram.add_be_int32(vertex->_color_index); + } + + } else if ((attributes & AM_has_packed_color) != 0) { + // We extract our own FltPackedColor instead of writing out the vertex's + // _packed_color directly, just in case the vertex is actually index + // colored. This bit of code will work regardless of the kind of color + // the vertex has. + + FltPackedColor color; + if (vertex->has_color()) { + color.set_color(vertex->get_color()); + } else { + // An uncolored vertex. Make it white. + color.set_color(LColor(1.0, 1.0, 1.0, 1.0)); + } + + if (!color.build_record(writer)) { + return false; + } + } + + if ((attributes & AM_has_normal) != 0) { + if (!vertex->_has_normal) { + datagram.add_be_float32(0.0); + datagram.add_be_float32(0.0); + datagram.add_be_float32(0.0); + } else { + datagram.add_be_float32(vertex->_normal[0]); + datagram.add_be_float32(vertex->_normal[1]); + datagram.add_be_float32(vertex->_normal[2]); + } + } + + if ((attributes & AM_has_base_uv) != 0) { + if (!vertex->_has_uv) { + datagram.add_be_float32(0.0); + datagram.add_be_float32(0.0); + } else { + datagram.add_be_float32(vertex->_uv[0]); + datagram.add_be_float32(vertex->_uv[1]); + } + } + } + + return true; +} diff --git a/pandatool/src/flt/fltLocalVertexPool.h b/pandatool/src/flt/fltLocalVertexPool.h new file mode 100644 index 00000000..f070dbc0 --- /dev/null +++ b/pandatool/src/flt/fltLocalVertexPool.h @@ -0,0 +1,77 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltLocalVertexPool.h + * @author drose + * @date 2001-02-28 + */ + +#ifndef FLTLOCALVERTEXPOOL_H +#define FLTLOCALVERTEXPOOL_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" +#include "fltHeader.h" +#include "fltVertex.h" + +#include "pointerTo.h" + +/** + * A local vertex pool, as might appear in the middle of the hierarchy, for + * instance for a mesh. + */ +class FltLocalVertexPool : public FltRecord { +public: + FltLocalVertexPool(FltHeader *header); + + // These bits are not stored in the vertex pool, but are read from the .flt + // file and used immediately. + enum AttributeMask { + AM_has_position = 0x80000000, + AM_has_color_index = 0x40000000, + AM_has_packed_color = 0x20000000, + AM_has_normal = 0x10000000, + AM_has_base_uv = 0x08000000, + AM_has_uv_1 = 0x04000000, + AM_has_uv_2 = 0x02000000, + AM_has_uv_3 = 0x01000000, + AM_has_uv_4 = 0x00800000, + AM_has_uv_5 = 0x00400000, + AM_has_uv_6 = 0x00200000, + AM_has_uv_7 = 0x00100000 + }; + + typedef pvector Vertices; + Vertices _vertices; + +public: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltLocalVertexPool", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltLocalVertexPool.I" + +#endif diff --git a/pandatool/src/flt/fltMaterial.cxx b/pandatool/src/flt/fltMaterial.cxx new file mode 100644 index 00000000..81411696 --- /dev/null +++ b/pandatool/src/flt/fltMaterial.cxx @@ -0,0 +1,164 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMaterial.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltMaterial.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltMaterial::_type_handle; + +/** + * + */ +FltMaterial:: +FltMaterial(FltHeader *header) : FltRecord(header) { + _material_index = -1; + _flags = 0; + _ambient.set(0.0, 0.0, 0.0); + _diffuse.set(0.0, 0.0, 0.0); + _specular.set(0.0, 0.0, 0.0); + _emissive.set(0.0, 0.0, 0.0); + _shininess = 0.0; + _alpha = 1.0; +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltMaterial:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_15_material, false); + DatagramIterator &iterator = reader.get_iterator(); + + _material_index = iterator.get_be_int32(); + _material_name = iterator.get_fixed_string(12); + _flags = iterator.get_be_uint32(); + _ambient[0] = iterator.get_be_float32(); + _ambient[1] = iterator.get_be_float32(); + _ambient[2] = iterator.get_be_float32(); + _diffuse[0] = iterator.get_be_float32(); + _diffuse[1] = iterator.get_be_float32(); + _diffuse[2] = iterator.get_be_float32(); + _specular[0] = iterator.get_be_float32(); + _specular[1] = iterator.get_be_float32(); + _specular[2] = iterator.get_be_float32(); + _emissive[0] = iterator.get_be_float32(); + _emissive[1] = iterator.get_be_float32(); + _emissive[2] = iterator.get_be_float32(); + _shininess = iterator.get_be_float32(); + _alpha = iterator.get_be_float32(); + iterator.skip_bytes(4); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltMaterial:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_15_material); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int32(_material_index); + datagram.add_fixed_string(_material_name, 12); + datagram.add_be_uint32(_flags); + datagram.add_be_float32(_ambient[0]); + datagram.add_be_float32(_ambient[1]); + datagram.add_be_float32(_ambient[2]); + datagram.add_be_float32(_diffuse[0]); + datagram.add_be_float32(_diffuse[1]); + datagram.add_be_float32(_diffuse[2]); + datagram.add_be_float32(_specular[0]); + datagram.add_be_float32(_specular[1]); + datagram.add_be_float32(_specular[2]); + datagram.add_be_float32(_emissive[0]); + datagram.add_be_float32(_emissive[1]); + datagram.add_be_float32(_emissive[2]); + datagram.add_be_float32(_shininess); + datagram.add_be_float32(_alpha); + datagram.pad_bytes(4); + + return true; +} + +/** + * Fills in the information in this record based on the information from the + * current position within the v14 material palette. Leaves the iterator at + * the beginning of the next material. + */ +bool FltMaterial:: +extract_14_record(int index, DatagramIterator &di) { + _material_index = index; + + _ambient[0] = di.get_be_float32(); + _ambient[1] = di.get_be_float32(); + _ambient[2] = di.get_be_float32(); + _diffuse[0] = di.get_be_float32(); + _diffuse[1] = di.get_be_float32(); + _diffuse[2] = di.get_be_float32(); + _specular[0] = di.get_be_float32(); + _specular[1] = di.get_be_float32(); + _specular[2] = di.get_be_float32(); + _emissive[0] = di.get_be_float32(); + _emissive[1] = di.get_be_float32(); + _emissive[2] = di.get_be_float32(); + _shininess = di.get_be_float32(); + _alpha = di.get_be_float32(); + _flags = di.get_be_uint32(); + _material_name = di.get_fixed_string(12); + di.skip_bytes(4 * 28); + + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, formatted as a part of a v14 material palette. Returns true on + * success, false if there is some error. + */ +bool FltMaterial:: +build_14_record(Datagram &datagram) { + datagram.add_be_float32(_ambient[0]); + datagram.add_be_float32(_ambient[1]); + datagram.add_be_float32(_ambient[2]); + datagram.add_be_float32(_diffuse[0]); + datagram.add_be_float32(_diffuse[1]); + datagram.add_be_float32(_diffuse[2]); + datagram.add_be_float32(_specular[0]); + datagram.add_be_float32(_specular[1]); + datagram.add_be_float32(_specular[2]); + datagram.add_be_float32(_emissive[0]); + datagram.add_be_float32(_emissive[1]); + datagram.add_be_float32(_emissive[2]); + datagram.add_be_float32(_shininess); + datagram.add_be_float32(_alpha); + datagram.add_be_uint32(_flags); + datagram.add_fixed_string(_material_name, 12); + datagram.pad_bytes(4 * 28); + + return true; +} diff --git a/pandatool/src/flt/fltMaterial.h b/pandatool/src/flt/fltMaterial.h new file mode 100644 index 00000000..fadc3acc --- /dev/null +++ b/pandatool/src/flt/fltMaterial.h @@ -0,0 +1,74 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMaterial.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTMATERIAL_H +#define FLTMATERIAL_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "luse.h" + +class DatagramIterator; + +/** + * Represents a single material in the material palette. + */ +class FltMaterial : public FltRecord { +public: + FltMaterial(FltHeader *header); + + enum Flags { + F_materials_used = 0x80000000, + }; + + int _material_index; + std::string _material_name; + unsigned int _flags; + LRGBColor _ambient; + LRGBColor _diffuse; + LRGBColor _specular; + LRGBColor _emissive; + PN_stdfloat _shininess; + PN_stdfloat _alpha; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + bool extract_14_record(int index, DatagramIterator &di); + bool build_14_record(Datagram &datagram); + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltMaterial", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif diff --git a/pandatool/src/flt/fltMesh.I b/pandatool/src/flt/fltMesh.I new file mode 100644 index 00000000..d1d9c0de --- /dev/null +++ b/pandatool/src/flt/fltMesh.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMesh.I + * @author drose + * @date 2001-02-28 + */ diff --git a/pandatool/src/flt/fltMesh.cxx b/pandatool/src/flt/fltMesh.cxx new file mode 100644 index 00000000..e77b8636 --- /dev/null +++ b/pandatool/src/flt/fltMesh.cxx @@ -0,0 +1,110 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMesh.cxx + * @author drose + * @date 2001-02-28 + */ + +#include "fltMesh.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" +#include "config_flt.h" + +TypeHandle FltMesh::_type_handle; + +/** + * + */ +FltMesh:: +FltMesh(FltHeader *header) : FltGeometry(header) { +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltMesh:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + DatagramIterator &iterator = reader.get_iterator(); + iterator.skip_bytes(4); // Undocumented padding. + + if (!FltGeometry::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_mesh, false); + + check_remaining_size(iterator); + return true; +} + +/** + * Checks whether the given bead, which follows this bead sequentially in the + * file, is an ancillary record of this bead. If it is, extracts the relevant + * information and returns true; otherwise, leaves it alone and returns false. + */ +bool FltMesh:: +extract_ancillary(FltRecordReader &reader) { + if (reader.get_opcode() == FO_local_vertex_pool) { + _vpool = new FltLocalVertexPool(_header); + return _vpool->extract_record(reader); + } + + return FltBeadID::extract_ancillary(reader); +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltMesh:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + Datagram &datagram = writer.update_datagram(); + datagram.pad_bytes(4); // Undocumented padding. + + if (!FltGeometry::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_mesh); + + return true; +} + +/** + * Writes whatever ancillary records are required for this record. Returns + * FE_ok on success, or something else if there is some error. + */ +FltError FltMesh:: +write_ancillary(FltRecordWriter &writer) const { + if (_vpool != nullptr) { + if (!_vpool->build_record(writer)) { + assert(!flt_error_abort); + return FE_bad_data; + } + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + return FltBeadID::write_ancillary(writer); +} diff --git a/pandatool/src/flt/fltMesh.h b/pandatool/src/flt/fltMesh.h new file mode 100644 index 00000000..a66bdf62 --- /dev/null +++ b/pandatool/src/flt/fltMesh.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMesh.h + * @author drose + * @date 2001-02-28 + */ + +#ifndef FLTMESH_H +#define FLTMESH_H + +#include "pandatoolbase.h" + +#include "fltGeometry.h" +#include "fltLocalVertexPool.h" + +#include "pointerTo.h" + +/** + * A mesh of connected polygons and tristrips, etc., with a local vertex pool. + */ +class FltMesh : public FltGeometry { +public: + FltMesh(FltHeader *header); + + PT(FltLocalVertexPool) _vpool; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltGeometry::init_type(); + register_type(_type_handle, "FltMesh", + FltGeometry::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltMesh.I" + +#endif diff --git a/pandatool/src/flt/fltMeshPrimitive.I b/pandatool/src/flt/fltMeshPrimitive.I new file mode 100644 index 00000000..70923ab8 --- /dev/null +++ b/pandatool/src/flt/fltMeshPrimitive.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMeshPrimitive.I + * @author drose + * @date 2001-02-28 + */ diff --git a/pandatool/src/flt/fltMeshPrimitive.cxx b/pandatool/src/flt/fltMeshPrimitive.cxx new file mode 100644 index 00000000..6db78c80 --- /dev/null +++ b/pandatool/src/flt/fltMeshPrimitive.cxx @@ -0,0 +1,126 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMeshPrimitive.cxx + * @author drose + * @date 2001-02-28 + */ + +#include "fltMeshPrimitive.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltMaterial.h" + +TypeHandle FltMeshPrimitive::_type_handle; + +/** + * + */ +FltMeshPrimitive:: +FltMeshPrimitive(FltHeader *header) : FltBead(header) { + _primitive_type = PT_tristrip; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltMeshPrimitive:: +extract_record(FltRecordReader &reader) { + if (!FltBead::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_mesh_primitive, false); + DatagramIterator &iterator = reader.get_iterator(); + + _primitive_type = (PrimitiveType)iterator.get_be_int16(); + + int vertex_width = iterator.get_be_int16(); + int num_vertices = iterator.get_be_int32(); + + if (vertex_width == 1) { + for (int i = 0; i < num_vertices; i++) { + _vertices.push_back(iterator.get_uint8()); + } + + } else if (vertex_width == 2) { + for (int i = 0; i < num_vertices; i++) { + _vertices.push_back(iterator.get_be_uint16()); + } + + } else if (vertex_width == 4) { + for (int i = 0; i < num_vertices; i++) { + _vertices.push_back(iterator.get_be_int32()); + } + + } else { + nout << "Invalid vertex width in mesh primitive: " << vertex_width + << "\n"; + return false; + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltMeshPrimitive:: +build_record(FltRecordWriter &writer) const { + if (!FltBead::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_mesh_primitive); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_primitive_type); + + // Determine the optimum index width, based on the largest vertex index. + int max_index = 0; + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + max_index = std::max(max_index, (*vi)); + } + + int vertex_width; + if (max_index < 0x100) { + vertex_width = 1; + } else if (max_index < 0x10000) { + vertex_width = 2; + } else { + vertex_width = 4; + } + + datagram.add_be_int16(vertex_width); + datagram.add_be_int32(_vertices.size()); + + if (vertex_width == 1) { + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + datagram.add_uint8(*vi); + } + + } else if (vertex_width == 2) { + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + datagram.add_be_uint16(*vi); + } + + } else { + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + datagram.add_be_int32(*vi); + } + } + + return true; +} diff --git a/pandatool/src/flt/fltMeshPrimitive.h b/pandatool/src/flt/fltMeshPrimitive.h new file mode 100644 index 00000000..bca21cc4 --- /dev/null +++ b/pandatool/src/flt/fltMeshPrimitive.h @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltMeshPrimitive.h + * @author drose + * @date 2001-02-28 + */ + +#ifndef FLTMESHPRIMITIVE_H +#define FLTMESHPRIMITIVE_H + +#include "pandatoolbase.h" + +#include "fltBead.h" +#include "fltHeader.h" + +#include "luse.h" +#include "vector_int.h" + +/** + * A single primitive of a mesh, like a triangle strip or fan. + */ +class FltMeshPrimitive : public FltBead { +public: + FltMeshPrimitive(FltHeader *header); + + enum PrimitiveType { + PT_tristrip = 1, + PT_trifan = 2, + PT_quadstrip = 3, + PT_polygon = 4, + }; + + typedef vector_int Vertices; + + PrimitiveType _primitive_type; + Vertices _vertices; + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBead::init_type(); + register_type(_type_handle, "FltMeshPrimitive", + FltBead::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "fltMeshPrimitive.I" + +#endif diff --git a/pandatool/src/flt/fltObject.cxx b/pandatool/src/flt/fltObject.cxx new file mode 100644 index 00000000..ce809741 --- /dev/null +++ b/pandatool/src/flt/fltObject.cxx @@ -0,0 +1,76 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltObject.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltObject.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltObject::_type_handle; + +/** + * + */ +FltObject:: +FltObject(FltHeader *header) : FltBeadID(header) { +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltObject:: +extract_record(FltRecordReader &reader) { + if (!FltBeadID::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_object, false); + DatagramIterator &iterator = reader.get_iterator(); + + _flags = iterator.get_be_uint32(); + _relative_priority = iterator.get_be_int16(); + _transparency = iterator.get_be_int16(); + _special_id1 = iterator.get_be_int16(); + _special_id2 = iterator.get_be_int16(); + _significance = iterator.get_be_int16(); + iterator.skip_bytes(2); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltObject:: +build_record(FltRecordWriter &writer) const { + if (!FltBeadID::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_object); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_uint32(_flags); + datagram.add_be_int16(_relative_priority); + datagram.add_be_int16(_transparency); + datagram.add_be_int16(_special_id1); + datagram.add_be_int16(_special_id2); + datagram.add_be_int16(_significance); + datagram.pad_bytes(2); + + return true; +} diff --git a/pandatool/src/flt/fltObject.h b/pandatool/src/flt/fltObject.h new file mode 100644 index 00000000..35a77ce7 --- /dev/null +++ b/pandatool/src/flt/fltObject.h @@ -0,0 +1,65 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltObject.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTOBJECT_H +#define FLTOBJECT_H + +#include "pandatoolbase.h" + +#include "fltBeadID.h" + +/** + * The main objecting bead of the flt file. + */ +class FltObject : public FltBeadID { +public: + FltObject(FltHeader *header); + + enum Flags { + F_no_daylight = 0x80000000, + F_no_dusk = 0x40000000, + F_no_night = 0x20000000, + F_no_illuminate = 0x10000000, + F_flat_shaded = 0x08000000, + F_shadow_object = 0x04000000, + }; + + unsigned int _flags; + int _relative_priority; + int _transparency; + int _special_id1, _special_id2; + int _significance; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltBeadID::init_type(); + register_type(_type_handle, "FltObject", + FltBeadID::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltOpcode.cxx b/pandatool/src/flt/fltOpcode.cxx new file mode 100644 index 00000000..92fd64af --- /dev/null +++ b/pandatool/src/flt/fltOpcode.cxx @@ -0,0 +1,297 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltOpcode.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltOpcode.h" + +std::ostream & +operator << (std::ostream &out, FltOpcode opcode) { + switch (opcode) { + case FO_none: + return out << "null opcode"; + + case FO_header: + return out << "header"; + + case FO_group: + return out << "group"; + + case FO_OB_scale: + case FO_OB_scale2: + case FO_OB_scale3: + return out << "(obsolete) scale"; + + case FO_object: + return out << "object"; + + case FO_face: + return out << "face"; + + case FO_OB_vertex_i: + return out << "(obsolete) vertex with ID"; + + case FO_OB_short_vertex: + return out << "(obsolete) short vertex"; + + case FO_OB_vertex_c: + return out << "(obsolete) vertex with color"; + + case FO_OB_vertex_cn: + return out << "(obsolete) vertex with color and normal"; + + case FO_push: + return out << "push"; + + case FO_pop: + return out << "pop"; + + case FO_OB_translate: + case FO_OB_translate2: + case FO_OB_translate3: + return out << "(obsolete) translate"; + + case FO_OB_dof: + return out << "(obsolete) degree-of-freedom"; + + case FO_dof: + return out << "degree-of-freedom"; + + case FO_OB_instance_ref: + return out << "(obsolete) instance reference"; + + case FO_OB_instance: + return out << "(obsolete) instance definition"; + + case FO_push_face: + return out << "push subface"; + + case FO_pop_face: + return out << "pop subface"; + + case FO_push_extension: + return out << "push extension"; + + case FO_pop_extension: + return out << "pop extension"; + + case FO_continuation: + return out << "continuation"; + + case FO_comment: + return out << "comment"; + + case FO_color_palette: + return out << "color palette"; + + case FO_long_id: + return out << "long ID"; + + case FO_transform_matrix: + return out << "transformation matrix"; + + case FO_OB_rotate_point: + case FO_OB_rotate_point2: + return out << "(obsolete) rotate about point"; + + case FO_OB_rotate_edge: + return out << "(obsolete) rotate about edge"; + + case FO_OB_nu_scale: + return out << "(obsolete) non-uniform scale"; + + case FO_OB_rotate_to_point: + return out << "(obsolete) rotate to point"; + + case FO_OB_put: + return out << "(obsolete) put"; + + case FO_OB_bounding_box: + return out << "(obsolete) bounding box"; + + case FO_vector: + return out << "vector"; + + case FO_multitexture: + return out << "multitexture"; + + case FO_uv_list: + return out << "UV list"; + + case FO_bsp: + return out << "BSP"; + + case FO_replicate: + return out << "replicate"; + + case FO_instance_ref: + return out << "instance reference"; + + case FO_instance: + return out << "instance definition"; + + case FO_external_ref: + return out << "external reference"; + + case FO_texture: + return out << "texture"; + + case FO_OB_eyepoint_palette: + return out << "(obsolete) eyepoint palette"; + + case FO_14_material_palette: + return out << "v14 material palette"; + + case FO_vertex_palette: + return out << "vertex palette"; + + case FO_vertex_c: + return out << "vertex with color"; + + case FO_vertex_cn: + return out << "vertex with color and normal"; + + case FO_vertex_cnu: + return out << "vertex with color, normal, and uv"; + + case FO_vertex_cu: + return out << "vertex with color and uv"; + + case FO_vertex_list: + return out << "vertex list"; + + case FO_lod: + return out << "LOD"; + + case FO_bounding_box: + return out << "bounding box"; + + case FO_rotate_about_edge: + return out << "rotate about edge"; + + case FO_translate: + return out << "translate"; + + case FO_scale: + return out << "scale"; + + case FO_rotate_about_point: + return out << "rotate about point"; + + case FO_rotate_and_scale: + return out << "rotate and/or scale"; + + case FO_put: + return out << "put"; + + case FO_eyepoint_palette: + return out << "eyepoint palette"; + + case FO_mesh: + return out << "mesh"; + + case FO_local_vertex_pool: + return out << "local vertex pool"; + + case FO_mesh_primitive: + return out << "mesh primitive"; + + case FO_road_segment: + return out << "road segment"; + + case FO_road_zone: + return out << "road zone"; + + case FO_morph_list: + return out << "morph vertex list"; + + case FO_behavior_palette: + return out << "behavior palette"; + + case FO_sound: + return out << "sound"; + + case FO_road_path: + return out << "road path"; + + case FO_sound_palette: + return out << "sound palette"; + + case FO_general_matrix: + return out << "general matrix"; + + case FO_text: + return out << "text"; + + case FO_switch: + return out << "switch"; + + case FO_line_style: + return out << "line style"; + + case FO_clip_region: + return out << "clip region"; + + case FO_light_source: + return out << "light source"; + + case FO_light_definition: + return out << "light source definition"; + + case FO_bounding_sphere: + return out << "bounding sphere"; + + case FO_bounding_cylinder: + return out << "bounding cylinder"; + + case FO_bv_center: + return out << "bounding volume center"; + + case FO_bv_orientation: + return out << "bounding volume orientation"; + + case FO_light_point: + return out << "light point"; + + case FO_texture_map_palette: + return out << "texture mapping palette"; + + case FO_15_material: + return out << "material"; + + case FO_name_table: + return out << "name table"; + + case FO_cat: + return out << "continuously adaptive terrain"; + + case FO_cat_data: + return out << "CAT Data"; + + case FO_push_attribute: + return out << "push attribute"; + + case FO_pop_attribute: + return out << "pop attribute"; + + case FO_adaptive_attribute: + return out << "adaptive attribute"; + + case FO_curve: + return out << "curve"; + + case FO_road_construction: + return out << "road construction"; + + default: + return out << "unknown opcode " << (int)opcode; + } +} diff --git a/pandatool/src/flt/fltOpcode.h b/pandatool/src/flt/fltOpcode.h new file mode 100644 index 00000000..f9bbeb9e --- /dev/null +++ b/pandatool/src/flt/fltOpcode.h @@ -0,0 +1,122 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltOpcode.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTOPCODE_H +#define FLTOPCODE_H + +#include "pandatoolbase.h" + +// Known opcodes, as of the latest version of flt. +enum FltOpcode { + FO_none = 0, + FO_header = 1, + FO_group = 2, + FO_OB_scale = 3, // obsolete + FO_object = 4, + FO_face = 5, + FO_OB_vertex_i = 6, // obsolete + FO_OB_short_vertex = 7, // obsolete + FO_OB_vertex_c = 8, // obsolete + FO_OB_vertex_cn = 9, // obsolete + FO_push = 10, + FO_pop = 11, + FO_OB_translate = 12, // obsolete + FO_OB_dof = 13, // obsolete + FO_dof = 14, + FO_OB_instance_ref = 16, // obsolete + FO_OB_instance = 17, // obsolete + FO_push_face = 19, + FO_pop_face = 20, + FO_push_extension = 21, + FO_pop_extension = 22, + FO_continuation = 23, + + FO_comment = 31, + FO_color_palette = 32, + FO_long_id = 33, + FO_OB_translate2 = 40, // obsolete + FO_OB_rotate_point = 41, // obsolete + FO_OB_rotate_edge = 42, // obsolete + FO_OB_scale2 = 43, // obsolete + FO_OB_translate3 = 44, // obsolete + FO_OB_nu_scale = 45, // obsolete + FO_OB_rotate_point2 = 46, // obsolete + FO_OB_rotate_to_point = 47, // obsolete + FO_OB_put = 48, // obsolete + FO_transform_matrix = 49, + FO_vector = 50, + FO_OB_bounding_box = 51, // obsolete + FO_multitexture = 52, + FO_uv_list = 53, + FO_bsp = 55, + FO_replicate = 60, + FO_instance_ref = 61, + FO_instance = 62, + FO_external_ref = 63, + FO_texture = 64, + FO_OB_eyepoint_palette = 65, // obsolete + FO_14_material_palette = 66, + FO_vertex_palette = 67, + FO_vertex_c = 68, + FO_vertex_cn = 69, + FO_vertex_cnu = 70, + FO_vertex_cu = 71, + FO_vertex_list = 72, + FO_lod = 73, + FO_bounding_box = 74, + FO_rotate_about_edge = 76, + FO_OB_scale3 = 77, // obsolete + FO_translate = 78, + FO_scale = 79, + FO_rotate_about_point = 80, + FO_rotate_and_scale = 81, + FO_put = 82, + FO_eyepoint_palette = 83, + FO_mesh = 84, + FO_local_vertex_pool = 85, + FO_mesh_primitive = 86, + FO_road_segment = 87, + FO_road_zone = 88, + FO_morph_list = 89, + FO_behavior_palette = 90, + FO_sound = 91, + FO_road_path = 92, + FO_sound_palette = 93, + FO_general_matrix = 94, + FO_text = 95, + FO_switch = 96, + FO_line_style = 97, + FO_clip_region = 98, + FO_extension = 100, + FO_light_source = 101, + FO_light_definition = 102, + FO_bounding_sphere = 105, + FO_bounding_cylinder = 106, + FO_bv_center = 108, + FO_bv_orientation = 109, + FO_light_point = 111, + FO_texture_map_palette = 112, + FO_15_material = 113, + FO_name_table = 114, + FO_cat = 115, + FO_cat_data = 116, + FO_push_attribute = 122, + FO_pop_attribute = 123, + FO_adaptive_attribute = 125, + FO_curve = 126, + FO_road_construction = 127 +}; + +std::ostream &operator << (std::ostream &out, FltOpcode opcode); + +#endif diff --git a/pandatool/src/flt/fltPackedColor.I b/pandatool/src/flt/fltPackedColor.I new file mode 100644 index 00000000..50ae5906 --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.I @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltPackedColor.I + * @author drose + * @date 2000-08-25 + */ + +INLINE std::ostream & +operator << (std::ostream &out, const FltPackedColor &color) { + color.output(out); + return out; +} + + +/** + * + */ +INLINE FltPackedColor:: +FltPackedColor() { + _a = 0; + _b = 0; + _g = 0; + _r = 0; +} + +/** + * Returns the four-component color as a LColor, where each component is in + * the range [0, 1]. + */ +INLINE LColor FltPackedColor:: +get_color() const { + return LColor(_r / 255.0, _g / 255.0, _b / 255.0, _a / 255.0); +} + +/** + * Returns the three-component color as an LRGBColor (ignoring the alpha + * component), where each component is in the range [0, 1]. + */ +INLINE LRGBColor FltPackedColor:: +get_rgb() const { + return LRGBColor(_r / 255.0, _g / 255.0, _b / 255.0); +} + +/** + * Sets the color according to the indicated four-component LColor value + * (including alpha). + */ +INLINE void FltPackedColor:: +set_color(const LColor &color) { + _r = (int)floor(color[0] * 255.0); + _g = (int)floor(color[1] * 255.0); + _b = (int)floor(color[2] * 255.0); + _a = (int)floor(color[3] * 255.0); +} + +/** + * Sets the color according to the indicated three-component LRGBColor value, + * and set the alpha to 1.0. + */ +INLINE void FltPackedColor:: +set_rgb(const LRGBColor &color) { + _r = (int)floor(color[0] * 255.0); + _g = (int)floor(color[1] * 255.0); + _b = (int)floor(color[2] * 255.0); + _a = 255; +} diff --git a/pandatool/src/flt/fltPackedColor.cxx b/pandatool/src/flt/fltPackedColor.cxx new file mode 100644 index 00000000..0faa4eb6 --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.cxx @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltPackedColor.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltPackedColor.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +/** + * + */ +void FltPackedColor:: +output(std::ostream &out) const { + out << "(" << _r << " " << _g << " " << _b << " " << _a << ")"; +} + +/** + * + */ +bool FltPackedColor:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _a = iterator.get_uint8(); + _b = iterator.get_uint8(); + _g = iterator.get_uint8(); + _r = iterator.get_uint8(); + + return true; +} + +/** + * + */ +bool FltPackedColor:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_uint8(_a); + datagram.add_uint8(_b); + datagram.add_uint8(_g); + datagram.add_uint8(_r); + + return true; +} diff --git a/pandatool/src/flt/fltPackedColor.h b/pandatool/src/flt/fltPackedColor.h new file mode 100644 index 00000000..1e38e02b --- /dev/null +++ b/pandatool/src/flt/fltPackedColor.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltPackedColor.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTPACKEDCOLOR_H +#define FLTPACKEDCOLOR_H + +#include "pandatoolbase.h" + +#include "luse.h" +#include + +class FltRecordReader; +class FltRecordWriter; + +/** + * A packed color record, A, B, G, R. This appears, for instance, within a + * face bead. + */ +class FltPackedColor { +public: + INLINE FltPackedColor(); + + INLINE LColor get_color() const; + INLINE LRGBColor get_rgb() const; + INLINE void set_color(const LColor &color); + INLINE void set_rgb(const LRGBColor &rgb); + + void output(std::ostream &out) const; + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + int _a; + int _b; + int _g; + int _r; +}; + +INLINE std::ostream &operator << (std::ostream &out, const FltPackedColor &color); + +#include "fltPackedColor.I" + +#endif diff --git a/pandatool/src/flt/fltRecord.I b/pandatool/src/flt/fltRecord.I new file mode 100644 index 00000000..add78f2d --- /dev/null +++ b/pandatool/src/flt/fltRecord.I @@ -0,0 +1,18 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecord.I + * @author drose + * @date 2000-08-24 + */ + +INLINE std::ostream & +operator << (std::ostream &out, const FltRecord &record) { + record.output(out); + return out; +} diff --git a/pandatool/src/flt/fltRecord.cxx b/pandatool/src/flt/fltRecord.cxx new file mode 100644 index 00000000..9fcc52fc --- /dev/null +++ b/pandatool/src/flt/fltRecord.cxx @@ -0,0 +1,746 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecord.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltRecord.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "fltGroup.h" +#include "fltObject.h" +#include "fltFace.h" +#include "fltCurve.h" +#include "fltMesh.h" +#include "fltLocalVertexPool.h" +#include "fltMeshPrimitive.h" +#include "fltVertexList.h" +#include "fltLOD.h" +#include "fltInstanceDefinition.h" +#include "fltInstanceRef.h" +#include "fltUnsupportedRecord.h" +#include "fltExternalReference.h" +#include "fltVectorRecord.h" +#include "config_flt.h" + +#include "dcast.h" +#include "indent.h" +#include "datagramIterator.h" + +#include + +TypeHandle FltRecord::_type_handle; + +/** + * + */ +FltRecord:: +FltRecord(FltHeader *header) : + _header(header) +{ +} + +/** + * + */ +FltRecord:: +~FltRecord() { +} + +/** + * Returns the number of child records of this record. This reflects the + * normal scene graph hierarchy. + */ +int FltRecord:: +get_num_children() const { + return _children.size(); +} + +/** + * Returns the nth child of this record. + */ +FltRecord *FltRecord:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), nullptr); + return _children[n]; +} + +/** + * Removes all children from this record. + */ +void FltRecord:: +clear_children() { + _children.clear(); +} + +/** + * Adds a new child to the end of the list of children for this record. + */ +void FltRecord:: +add_child(FltRecord *child) { + _children.push_back(child); +} + +/** + * Returns the number of subface records of this record. Normally, subfaces + * will only be present on object records, although it is logically possible + * for them to appear anywhere. + */ +int FltRecord:: +get_num_subfaces() const { + return _subfaces.size(); +} + +/** + * Returns the nth subface of this record. + */ +FltRecord *FltRecord:: +get_subface(int n) const { + nassertr(n >= 0 && n < (int)_subfaces.size(), nullptr); + return _subfaces[n]; +} + +/** + * Removes all subfaces from this record. + */ +void FltRecord:: +clear_subfaces() { + _subfaces.clear(); +} + +/** + * Adds a new subface to the end of the list of subfaces for this record. + */ +void FltRecord:: +add_subface(FltRecord *subface) { + _subfaces.push_back(subface); +} + +/** + * Returns the number of extension attribute records for this object. These + * are auxiliary nodes, presumably of type FO_extension, that have some local + * meaning to the object. + */ +int FltRecord:: +get_num_extensions() const { + return _extensions.size(); +} + +/** + * Returns the nth extension of this record. + */ +FltRecord *FltRecord:: +get_extension(int n) const { + nassertr(n >= 0 && n < (int)_extensions.size(), nullptr); + return _extensions[n]; +} + +/** + * Removes all extensions from this record. + */ +void FltRecord:: +clear_extensions() { + _extensions.clear(); +} + +/** + * Adds a new extension to the end of the list of extensions for this record. + * This should be a record of type FO_extension. + */ +void FltRecord:: +add_extension(FltRecord *extension) { + _extensions.push_back(extension); +} + +/** + * Returns the number of unsupported ancillary records of this record. These + * are ancillary records that appeared following this record in the flt file + * but that aren't directly understood by the flt loader--normally, an + * ancillary record is examined and decoded on the spot, and no pointer to it + * is kept. + */ +int FltRecord:: +get_num_ancillary() const { + return _ancillary.size(); +} + +/** + * Returns the nth unsupported ancillary record of this record. See + * get_num_ancillary(). + */ +FltRecord *FltRecord:: +get_ancillary(int n) const { + nassertr(n >= 0 && n < (int)_ancillary.size(), nullptr); + return _ancillary[n]; +} + +/** + * Removes all unsupported ancillary records from this record. See + * get_num_ancillary(). + */ +void FltRecord:: +clear_ancillary() { + _ancillary.clear(); +} + +/** + * Adds a new unsupported ancillary record to the end of the list of ancillary + * records for this record. This record will be written to the flt file + * following this record, without attempting to understand what is in it. + * + * Normally, there is no reason to use this function; if the data stored in + * the FltRecord requires one or more ancillary record, the appropriate + * records will automatically be generated when the record is written. This + * function is only required to output a record whose type is not supported by + * the flt loader. But it would be better to extend the flt loader to know + * about this new kind of data record. + */ +void FltRecord:: +add_ancillary(FltRecord *ancillary) { + _ancillary.push_back(ancillary); +} + +/** + * Returns true if this record has a nonempty comment, false otherwise. + */ +bool FltRecord:: +has_comment() const { + return !_comment.empty(); +} + +/** + * Retrieves the comment for this record, or empty string if the record has no + * comment. + */ +const std::string &FltRecord:: +get_comment() const { + return _comment; +} + +/** + * Removes the comment for this record. + */ +void FltRecord:: +clear_comment() { + _comment = ""; +} + +/** + * Changes the comment for this record. + */ +void FltRecord:: +set_comment(const std::string &comment) { + _comment = comment; +} + +/** + * Checks that the iterator has no bytes left, as it should at the end of a + * successfully read record. If there *are* remaining bytes, print a warning + * message but otherwise don't worry about it. + * + * If we are attempting to read a flt file whose version is newer than the + * newest this program understands, don't even print a warning message, since + * this is exactly the sort of thing we expect. + */ +void FltRecord:: +check_remaining_size(const DatagramIterator &di, const std::string &name) const { + if (di.get_remaining_size() == 0) { + return; + } + + if (_header->get_flt_version() <= _header->max_flt_version()) { + nout << "Warning! Ignoring extra " << di.get_remaining_size() + << " bytes at the end of a "; + if (name.empty()) { + nout << get_type(); + } else { + nout << name; + } + nout << " record.\n"; + } +} + +/** + * Walks the hierarchy at this record and below and copies the + * _converted_filename record into the _orig_filename record, so the flt file + * will be written out with the converted filename instead of what was + * originally read in. + */ +void FltRecord:: +apply_converted_filenames() { + Records::const_iterator ci; + for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) { + (*ci)->apply_converted_filenames(); + } + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->apply_converted_filenames(); + } +} + +/** + * Writes a quick one-line description of the record, but not its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltRecord:: +output(std::ostream &out) const { + out << get_type(); +} + +/** + * Writes a multiple-line description of the record and all of its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltRecord:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) << *this; + write_children(out, indent_level); +} + +/** + * Assuming the current write position has been left at the end of the last + * line of the record description, writes out the list of children. + */ +void FltRecord:: +write_children(std::ostream &out, int indent_level) const { + if (!_ancillary.empty()) { + out << " + " << _ancillary.size() << " ancillary"; + } + if (!_extensions.empty()) { + out << " + " << _extensions.size() << " extensions"; + } + if (!_subfaces.empty()) { + out << " ["; + Records::const_iterator ci; + for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) { + out << " " << *(*ci); + } + out << " ]"; + } + if (!_children.empty()) { + out << " {\n"; + Records::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write(out, indent_level + 2); + } + indent(out, indent_level) << "}\n"; + } else { + out << "\n"; + } +} + + /* + virtual void write(ostream &out) const; + virtual void build_record(Datagram &datagram) const; + */ + +/** + * Returns true if the indicated opcode corresponds to an ancillary record + * type, false otherwise. In general, this function is used to identify + * ancillary records that are not presently supported by the FltReader; these + * will be ignored. Normally, ancillary records will be detected and + * processed by extract_ancillary(). + */ +bool FltRecord:: +is_ancillary(FltOpcode opcode) { + switch (opcode) { + case FO_comment: + case FO_long_id: + case FO_multitexture: + case FO_uv_list: + case FO_replicate: + case FO_road_zone: + case FO_transform_matrix: + case FO_rotate_about_edge: + case FO_translate: + case FO_scale: + case FO_rotate_about_point: + case FO_rotate_and_scale: + case FO_put: + case FO_general_matrix: + case FO_vector: + case FO_bounding_box: + case FO_bounding_sphere: + case FO_bounding_cylinder: + case FO_bv_center: + case FO_bv_orientation: + case FO_local_vertex_pool: + case FO_cat_data: + + case FO_14_material_palette: + case FO_vertex_palette: + case FO_vertex_c: + case FO_vertex_cn: + case FO_vertex_cnu: + case FO_vertex_cu: + case FO_color_palette: + case FO_name_table: + case FO_15_material: + case FO_texture: + case FO_eyepoint_palette: + case FO_light_definition: + case FO_texture_map_palette: + return true; + + case FO_header: + case FO_mesh: + case FO_mesh_primitive: + case FO_group: + case FO_object: + case FO_face: + case FO_light_point: + case FO_dof: + case FO_vertex_list: + case FO_morph_list: + case FO_bsp: + case FO_external_ref: + case FO_lod: + case FO_sound: + case FO_light_source: + case FO_road_segment: + case FO_road_construction: + case FO_road_path: + case FO_clip_region: + case FO_text: + case FO_switch: + case FO_cat: + case FO_extension: + case FO_curve: + return false; + + case FO_push: + case FO_pop: + case FO_push_face: + case FO_pop_face: + case FO_push_attribute: + case FO_pop_attribute: + case FO_push_extension: + case FO_pop_extension: + case FO_instance: + case FO_instance_ref: + return false; + + default: + nout << "Don't know whether " << opcode << " is ancillary.\n"; + return false; + } +} + +/** + * Creates a new FltRecord corresponding to the opcode. If the opcode is + * unknown, creates a FltUnsupportedRecord. + */ +FltRecord *FltRecord:: +create_new_record(FltOpcode opcode) const { + switch (opcode) { + case FO_group: + return new FltGroup(_header); + + case FO_object: + return new FltObject(_header); + + case FO_face: + return new FltFace(_header); + + case FO_curve: + return new FltCurve(_header); + + case FO_mesh: + return new FltMesh(_header); + + case FO_local_vertex_pool: + return new FltLocalVertexPool(_header); + + case FO_mesh_primitive: + return new FltMeshPrimitive(_header); + + case FO_vertex_list: + return new FltVertexList(_header); + + case FO_lod: + return new FltLOD(_header); + + case FO_instance: + return new FltInstanceDefinition(_header); + + case FO_instance_ref: + return new FltInstanceRef(_header); + + case FO_external_ref: + return new FltExternalReference(_header); + + case FO_vector: + return new FltVectorRecord(_header); + + default: + nout << "Ignoring unsupported record " << opcode << "\n"; + return new FltUnsupportedRecord(_header); + } +} + +/** + * Extracts this record information from the current record presented in the + * reader, then advances the reader and continues to read any children, if + * present. On return, the reader is position on the next sibling record to + * this record. + * + * Returns FE_ok if successful, otherwise on error. + */ +FltError FltRecord:: +read_record_and_children(FltRecordReader &reader) { + if (!extract_record(reader)) { + nout << "Could not extract record for " << *this << "\n"; + assert(!flt_error_abort); + return FE_invalid_record; + } + FltError result = reader.advance(); + if (result == FE_end_of_file) { + return FE_ok; + } else if (result != FE_ok) { + return result; + } + + while (true) { + if (extract_ancillary(reader)) { + // Ok, a known ancillary record. Fine. + + } else if (reader.get_opcode() == FO_push) { + // A push begins a new list of children. + result = reader.advance(); + if (result != FE_ok) { + return result; + } + + while (reader.get_opcode() != FO_pop) { + PT(FltRecord) child = create_new_record(reader.get_opcode()); + FltError result = child->read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + + if (child->is_of_type(FltInstanceDefinition::get_class_type())) { + // A special case for an instance definition. These shouldn't + // appear in the hierarchy, but should instead be added directly to + // the header. + _header->add_instance(DCAST(FltInstanceDefinition, child)); + + } else { + add_child(child); + } + + if (reader.eof() || reader.error()) { + assert(!flt_error_abort); + return FE_end_of_file; + } + } + + } else if (reader.get_opcode() == FO_push_face) { + // A push subface begins a new list of subfaces. + result = reader.advance(); + if (result != FE_ok) { + return result; + } + + while (reader.get_opcode() != FO_pop_face) { + PT(FltRecord) subface = create_new_record(reader.get_opcode()); + FltError result = subface->read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + add_subface(subface); + if (reader.eof() || reader.error()) { + assert(!flt_error_abort); + return FE_end_of_file; + } + } + + } else if (reader.get_opcode() == FO_push_extension) { + // A push extension begins a new list of extensions. + result = reader.advance(); + if (result != FE_ok) { + return result; + } + + while (reader.get_opcode() != FO_pop_extension) { + PT(FltRecord) extension = create_new_record(reader.get_opcode()); + FltError result = extension->read_record_and_children(reader); + if (result != FE_ok) { + return result; + } + add_extension(extension); + if (reader.eof() || reader.error()) { + assert(!flt_error_abort); + return FE_end_of_file; + } + } + + } else if (is_ancillary(reader.get_opcode())) { + // An unsupported ancillary record. Skip it. + PT(FltRecord) ancillary = create_new_record(reader.get_opcode()); + ancillary->extract_record(reader); + _ancillary.push_back(ancillary); + + } else { + // None of the above: we're done. + return FE_ok; + } + + // Skip to the next record. If that's the end, fine. + result = reader.advance(true); + if (reader.eof() || result != FE_ok) { + return result; + } + } +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltRecord:: +extract_record(FltRecordReader &) { + return true; +} + +/** + * Checks whether the given record, which follows this record sequentially in + * the file, is an ancillary record of this record. If it is, extracts the + * relevant information and returns true; otherwise, leaves it alone and + * returns false. + */ +bool FltRecord:: +extract_ancillary(FltRecordReader &reader) { + if (reader.get_opcode() == FO_comment) { + DatagramIterator &di = reader.get_iterator(); + _comment = di.get_fixed_string(di.get_remaining_size()); + return true; + } + + return false; +} + +/** + * Writes this record out to the flt file, along with all of its ancillary + * records and children records. Returns FE_ok on success, or something else + * on error. + */ +FltError FltRecord:: +write_record_and_children(FltRecordWriter &writer) const { + // First, write the record. + if (!build_record(writer)) { + assert(!flt_error_abort); + return FE_bad_data; + } + + FltError result = writer.advance(); + if (result != FE_ok) { + return result; + } + + // Then the ancillary data. + result = write_ancillary(writer); + if (result != FE_ok) { + return result; + } + Records::const_iterator ci; + for (ci = _ancillary.begin(); ci != _ancillary.end(); ++ci) { + if (!(*ci)->build_record(writer)) { + assert(!flt_error_abort); + return FE_bad_data; + } + result = writer.advance(); + if (result != FE_ok) { + return result; + } + } + + // Any extensions? + if (!_extensions.empty()) { + result = writer.write_record(FO_push_face); + if (result != FE_ok) { + return result; + } + + for (ci = _extensions.begin(); ci != _extensions.end(); ++ci) { + (*ci)->write_record_and_children(writer); + } + + result = writer.write_record(FO_pop_face); + if (result != FE_ok) { + return result; + } + } + + // Finally, write all the children. + if (!_children.empty()) { + result = writer.write_record(FO_push); + if (result != FE_ok) { + return result; + } + + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write_record_and_children(writer); + } + + result = writer.write_record(FO_pop); + if (result != FE_ok) { + return result; + } + } + + // We must write subfaces *after* the list of children, or Creator will + // crash trying to load the file. + if (!_subfaces.empty()) { + result = writer.write_record(FO_push_face); + if (result != FE_ok) { + return result; + } + + for (ci = _subfaces.begin(); ci != _subfaces.end(); ++ci) { + (*ci)->write_record_and_children(writer); + } + + result = writer.write_record(FO_pop_face); + if (result != FE_ok) { + return result; + } + } + + return FE_ok; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltRecord:: +build_record(FltRecordWriter &) const { + return true; +} + +/** + * Writes whatever ancillary records are required for this record. Returns + * FE_ok on success, or something else if there is some error. + */ +FltError FltRecord:: +write_ancillary(FltRecordWriter &writer) const { + if (!_comment.empty()) { + Datagram dc(_comment.data(), _comment.size()); + FltError result = writer.write_record(FO_comment, dc); + if (result != FE_ok) { + return result; + } + } + return FE_ok; +} diff --git a/pandatool/src/flt/fltRecord.h b/pandatool/src/flt/fltRecord.h new file mode 100644 index 00000000..9fc8e3ad --- /dev/null +++ b/pandatool/src/flt/fltRecord.h @@ -0,0 +1,123 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecord.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTRECORD_H +#define FLTRECORD_H + +#include "pandatoolbase.h" + +#include "fltOpcode.h" +#include "fltError.h" + +#include "typedObject.h" +#include "typedReferenceCount.h" +#include "pointerTo.h" + +class FltHeader; +class FltRecordReader; +class FltRecordWriter; +class DatagramIterator; + +/** + * The base class for all kinds of records in a MultiGen OpenFlight file. A + * flt file consists of a hierarchy of "beads" of various kinds, each of which + * may be followed by n ancillary records, written sequentially to the file. + */ +class FltRecord : public TypedReferenceCount { +public: + FltRecord(FltHeader *header); + virtual ~FltRecord(); + + int get_num_children() const; + FltRecord *get_child(int n) const; + void clear_children(); + void add_child(FltRecord *child); + + int get_num_subfaces() const; + FltRecord *get_subface(int n) const; + void clear_subfaces(); + void add_subface(FltRecord *subface); + + int get_num_extensions() const; + FltRecord *get_extension(int n) const; + void clear_extensions(); + void add_extension(FltRecord *extension); + + int get_num_ancillary() const; + FltRecord *get_ancillary(int n) const; + void clear_ancillary(); + void add_ancillary(FltRecord *ancillary); + + bool has_comment() const; + const std::string &get_comment() const; + void clear_comment(); + void set_comment(const std::string &comment); + + void check_remaining_size(const DatagramIterator &di, + const std::string &name = std::string()) const; + + virtual void apply_converted_filenames(); + + virtual void output(std::ostream &out) const; + virtual void write(std::ostream &out, int indent_level = 0) const; + +protected: + void write_children(std::ostream &out, int indent_level) const; + + static bool is_ancillary(FltOpcode opcode); + + FltRecord *create_new_record(FltOpcode opcode) const; + FltError read_record_and_children(FltRecordReader &reader); + virtual bool extract_record(FltRecordReader &reader); + virtual bool extract_ancillary(FltRecordReader &reader); + + virtual FltError write_record_and_children(FltRecordWriter &writer) const; + virtual bool build_record(FltRecordWriter &writer) const; + virtual FltError write_ancillary(FltRecordWriter &writer) const; + +protected: + FltHeader *_header; + +private: + typedef pvector Records; + Records _children; + Records _subfaces; + Records _extensions; + Records _ancillary; + + std::string _comment; + + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "FltRecord", + TypedReferenceCount::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream &operator << (std::ostream &out, const FltRecord &record); + +#include "fltRecord.I" + +#endif diff --git a/pandatool/src/flt/fltRecordReader.cxx b/pandatool/src/flt/fltRecordReader.cxx new file mode 100644 index 00000000..8e1993c9 --- /dev/null +++ b/pandatool/src/flt/fltRecordReader.cxx @@ -0,0 +1,242 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecordReader.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltRecordReader.h" +#include "config_flt.h" + +#include "datagramIterator.h" + +#include + +/** + * + */ +FltRecordReader:: +FltRecordReader(std::istream &in) : + _in(in) +{ + _opcode = FO_none; + _record_length = 0; + _iterator = nullptr; + _state = S_begin; + _next_error = FE_ok; + _next_opcode = FO_none; + _next_record_length = 0; + + // Read the first header to get us going. + read_next_header(); +} + +/** + * + */ +FltRecordReader:: +~FltRecordReader() { + delete _iterator; + _iterator = nullptr; +} + +/** + * Returns the opcode associated with the current record. + */ +FltOpcode FltRecordReader:: +get_opcode() const { + nassertr(_state == S_normal, FO_none); + return _opcode; +} + +/** + * Returns an iterator suitable for extracting data from the current record. + */ +DatagramIterator &FltRecordReader:: +get_iterator() { + nassertr(_state == S_normal, *_iterator); + return *_iterator; +} + +/** + * Returns the datagram representing the entire record, less the four-byte + * header. + */ +const Datagram &FltRecordReader:: +get_datagram() { +#ifndef NDEBUG + static Datagram bogus_datagram; + nassertr(_state == S_normal, bogus_datagram); +#endif + return _iterator->get_datagram(); +} + +/** + * Returns the entire length of the record, including the four-byte header. + */ +int FltRecordReader:: +get_record_length() const { + return _record_length; +} + +/** + * Extracts the next record from the file. Returns true if there is another + * record, or false if the end of file has been reached. + */ +FltError FltRecordReader:: +advance(bool ok_eof) { + if (_state == S_eof) { + assert(!flt_error_abort); + return FE_end_of_file; + } + if (_state == S_error) { + assert(!flt_error_abort); + return FE_read_error; + } + if (_iterator != nullptr) { + delete _iterator; + _iterator = nullptr; + } + + if (_next_error == FE_end_of_file) { + _state = S_eof; + if (ok_eof) { + return FE_ok; + } + assert(!flt_error_abort); + return FE_end_of_file; + + } else if (_next_error != FE_ok) { + _state = S_error; + assert(!flt_error_abort); + return _next_error; + } + + _opcode = _next_opcode; + _record_length = _next_record_length; + + if (flt_cat.is_debug()) { + flt_cat.debug() + << "Reading " << _opcode + << " of length " << _record_length << "\n"; + } + + // And now read the full record based on the length. + int length = _next_record_length - header_size; + if (length > 0) { + vector_uchar data((size_t)length); + _in.read((char *)&data[0], length); + _datagram = Datagram(std::move(data)); + } else { + _datagram = Datagram(); + } + + if (_in.fail()) { + if (_in.eof()) { + _state = S_eof; + assert(!flt_error_abort); + return FE_end_of_file; + } + + _state = S_error; + assert(!flt_error_abort); + return FE_read_error; + } + + // Check out the next header in case it's a continuation. + read_next_header(); + while (_next_error == FE_ok && _next_opcode == FO_continuation) { + if (flt_cat.is_debug()) { + flt_cat.debug() + << "Reading continuation of length " << _next_record_length << "\n"; + } + + // Read the continuation and tack it on. + _record_length += _next_record_length; + length = _next_record_length - header_size; + + if (length > 0) { + char *buffer = new char[length]; + _in.read(buffer, length); + _datagram.append_data(buffer, length); + delete[] buffer; + } + + if (_in.fail()) { + if (_in.eof()) { + _state = S_eof; + assert(!flt_error_abort); + return FE_end_of_file; + } + + _state = S_error; + assert(!flt_error_abort); + return FE_read_error; + } + + read_next_header(); + } + + // Finally, create a new iterator to read this record. + _iterator = new DatagramIterator(_datagram); + _state = S_normal; + + return FE_ok; +} + +/** + * Returns true if end-of-file has been reached without error. + */ +bool FltRecordReader:: +eof() const { + return _state == S_eof; +} + +/** + * Returns true if some error has been encountered while reading (for + * instance, a truncated file). + */ +bool FltRecordReader:: +error() const { + return _state == S_error; +} + +/** + * Reads the four-byte header for the next record, which contains the next + * opcode and record length. + * + * We need read the next header in advance so we can check to see if it + * happens to be a continuation record. If it is, we will need to concatenate + * the records together before returning. + */ +void FltRecordReader:: +read_next_header() { + char bytes[header_size]; + _in.read(bytes, header_size); + + if (_in.fail()) { + if (_in.eof()) { + _next_error = FE_end_of_file; + return; + } + _next_error = FE_read_error; + return; + } + + // Now extract out the opcode and length. + Datagram dg(bytes, header_size); + DatagramIterator dgi(dg); + _next_opcode = (FltOpcode)dgi.get_be_int16(); + _next_record_length = dgi.get_be_uint16(); + + if (_next_record_length < header_size) { + _next_error = FE_invalid_record; + return; + } +} diff --git a/pandatool/src/flt/fltRecordReader.h b/pandatool/src/flt/fltRecordReader.h new file mode 100644 index 00000000..8817e800 --- /dev/null +++ b/pandatool/src/flt/fltRecordReader.h @@ -0,0 +1,67 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecordReader.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTRECORDREADER_H +#define FLTRECORDREADER_H + +#include "pandatoolbase.h" + +#include "fltOpcode.h" +#include "fltError.h" + +#include "datagram.h" +#include "datagramIterator.h" + +/** + * This class turns an istream into a sequence of FltRecords by reading a + * sequence of Datagrams and extracting the opcode from each one. It + * remembers where it is in the file and what the current record is. + */ +class FltRecordReader { +public: + FltRecordReader(std::istream &in); + ~FltRecordReader(); + + FltOpcode get_opcode() const; + DatagramIterator &get_iterator(); + const Datagram &get_datagram(); + int get_record_length() const; + + FltError advance(bool ok_eof = false); + + bool eof() const; + bool error() const; + +private: + void read_next_header(); + + std::istream &_in; + Datagram _datagram; + FltOpcode _opcode; + int _record_length; + DatagramIterator *_iterator; + + FltError _next_error; + FltOpcode _next_opcode; + int _next_record_length; + + enum State { + S_begin, + S_normal, + S_eof, + S_error + }; + State _state; +}; + +#endif diff --git a/pandatool/src/flt/fltRecordWriter.cxx b/pandatool/src/flt/fltRecordWriter.cxx new file mode 100644 index 00000000..909e5ce2 --- /dev/null +++ b/pandatool/src/flt/fltRecordWriter.cxx @@ -0,0 +1,151 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecordWriter.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltRecordWriter.h" +#include "fltInstanceDefinition.h" +#include "fltHeader.h" +#include "config_flt.h" + +#include "datagram.h" + +#include + +// Don't attempt to write more than this number of bytes in one record. If +// the record requires more than this, use continuation records. +static const int max_write_length = 65532; + +/** + * + */ +FltRecordWriter:: +FltRecordWriter(std::ostream &out) : + _out(out) +{ +} + +/** + * + */ +FltRecordWriter:: +~FltRecordWriter() { +} + +/** + * Sets the opcode associated with the current record. + */ +void FltRecordWriter:: +set_opcode(FltOpcode opcode) { + _opcode = opcode; +} + +/** + * Sets the datagram that will be written when advance() is called. + */ +void FltRecordWriter:: +set_datagram(const Datagram &datagram) { + _datagram = datagram; +} + +/** + * Returns a modifiable reference to the datagram associated with the current + * record. This datagram should then be stuffed with data corresponding to + * the data in the record, in preparation for calling advance() to write the + * data. + */ +Datagram &FltRecordWriter:: +update_datagram() { + return _datagram; +} + +/** + * Writes the current record to the flt file, and resets the current record to + * receive new data. Returns FE_ok on success, or something else on error. + */ +FltError FltRecordWriter:: +advance() { + int start_byte = 0; + int write_length = + std::min((int)_datagram.get_length() - start_byte, max_write_length - header_size); + FltOpcode opcode = _opcode; + + do { + if (flt_cat.is_debug()) { + flt_cat.debug() + << "Writing " << opcode << " of length " + << write_length + header_size << "\n"; + } + + // Build a mini-datagram to write the header. + Datagram dg; + dg.add_be_int16(opcode); + dg.add_be_int16(write_length + header_size); + + nassertr((int)dg.get_length() == header_size, FE_internal); + + _out.write((const char *)dg.get_data(), dg.get_length()); + if (_out.fail()) { + assert(!flt_error_abort); + return FE_write_error; + } + + // Now write the rest of the record. + _out.write((const char *)_datagram.get_data() + start_byte, write_length); + if (_out.fail()) { + assert(!flt_error_abort); + return FE_write_error; + } + + start_byte += write_length; + write_length = + std::min((int)_datagram.get_length() - start_byte, max_write_length - header_size); + opcode = FO_continuation; + } while (write_length > 0); + + _datagram.clear(); + _opcode = FO_none; + + return FE_ok; +} + +/** + * A convenience function to quickly write a simple record that consists of an + * opcode and possibly a datagram. + */ +FltError FltRecordWriter:: +write_record(FltOpcode opcode, const Datagram &datagram) { + _opcode = opcode; + _datagram = datagram; + return advance(); +} + +/** + * Ensures that the given instance definition has already been written to the + * file. If it has not, writes it now. + */ +FltError FltRecordWriter:: +write_instance_def(FltHeader *header, int instance_index) { + bool inserted = _instances_written.insert(instance_index).second; + + if (!inserted) { + // It's already been written. + return FE_ok; + } + + FltInstanceDefinition *instance = header->get_instance(instance_index); + if (instance == nullptr) { + assert(!flt_error_abort); + return FE_undefined_instance; + } + + return instance->write_record_and_children(*this); +} diff --git a/pandatool/src/flt/fltRecordWriter.h b/pandatool/src/flt/fltRecordWriter.h new file mode 100644 index 00000000..8cd4e4b6 --- /dev/null +++ b/pandatool/src/flt/fltRecordWriter.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltRecordWriter.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTRECORDWRITER_H +#define FLTRECORDWRITER_H + +#include "pandatoolbase.h" + +#include "fltOpcode.h" +#include "fltError.h" + +#include "datagram.h" +#include "pset.h" + +class FltHeader; + +/** + * This class writes a sequence of FltRecords to an ostream, handling opcode + * and size counts properly. + */ +class FltRecordWriter { +public: + FltRecordWriter(std::ostream &out); + ~FltRecordWriter(); + + void set_opcode(FltOpcode opcode); + const Datagram &get_datagram() const; + void set_datagram(const Datagram &datagram); + Datagram &update_datagram(); + + FltError advance(); + + FltError write_record(FltOpcode opcode, + const Datagram &datagram = Datagram()); + + FltError write_instance_def(FltHeader *header, int instance_index); + +private: + std::ostream &_out; + Datagram _datagram; + FltOpcode _opcode; + + typedef pset Instances; + Instances _instances_written; +}; + +#endif diff --git a/pandatool/src/flt/fltTexture.cxx b/pandatool/src/flt/fltTexture.cxx new file mode 100644 index 00000000..c65f5a05 --- /dev/null +++ b/pandatool/src/flt/fltTexture.cxx @@ -0,0 +1,475 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTexture.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltTexture.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" +#include "pathReplace.h" +#include "config_putil.h" + +TypeHandle FltTexture::_type_handle; + +/** + * + */ +FltTexture:: +FltTexture(FltHeader *header) : FltRecord(header) { + _pattern_index = -1; + _x_location = 0; + _y_location = 0; + + _num_texels_u = 0; + _num_texels_v = 0; + _real_world_size_u = 0; + _real_world_size_v = 0; + _up_vector_x = 0; + _up_vector_y = 1; + _file_format = FF_none; + _min_filter = MN_point; + _mag_filter = MG_point; + _repeat = RT_repeat; + _repeat_u = RT_repeat; + _repeat_v = RT_repeat; + _modify_flag = 0; + _x_pivot_point = 0; + _y_pivot_point = 0; + _env_type = ET_modulate; + _intensity_is_alpha = false; + _float_real_world_size_u = 0.0; + _float_real_world_size_v = 0.0; + _imported_origin_code = 0; + _kernel_version = 1520; + _internal_format = IF_default; + _external_format = EF_default; + _use_mipmap_kernel = false; + memset(_mipmap_kernel, 0, sizeof(_mipmap_kernel)); + _use_lod_scale = false; + memset(_lod_scale, 0, sizeof(_lod_scale)); + _clamp = 0.0; + _mag_filter_alpha = MG_point; + _mag_filter_color = MG_point; + _lambert_conic_central_meridian = 0.0; + _lambert_conic_upper_latitude = 0.0; + _lambert_conic_lower_latitude = 0.0; + _use_detail = false; + _detail_j = 0; + _detail_k = 0; + _detail_m = 0; + _detail_n = 0; + _detail_scramble = 0; + _use_tile = false; + _tile_lower_left_u = 0.0; + _tile_lower_left_v = 0.0; + _tile_upper_right_u = 0.0; + _tile_upper_right_v = 0.0; + _projection = PT_flat_earth; + _earth_model = EM_wgs84; + _utm_zone = 0; + _image_origin = IO_lower_left; + _geospecific_points_units = PU_degrees; + _geospecific_hemisphere = H_southern; + _file_version = 1501; +} + +/** + * Walks the hierarchy at this record and below and copies the + * _converted_filename record into the _orig_filename record, so the flt file + * will be written out with the converted filename instead of what was + * originally read in. + */ +void FltTexture:: +apply_converted_filenames() { + _orig_filename = _converted_filename.to_os_generic(); + FltRecord::apply_converted_filenames(); +} + +/** + * Returns the name of the texture image file. + */ +Filename FltTexture:: +get_texture_filename() const { + return _converted_filename; +} + +/** + * Changes the name of the texture image file. + */ +void FltTexture:: +set_texture_filename(const Filename &filename) { + _converted_filename = filename; + _orig_filename = _converted_filename.to_os_generic(); +} + +/** + * Returns the name of the texture's associated .attr file. This contains + * some additional MultiGen information about the texture parameters. This + * is, of course, just the name of the texture with .attr appended. + * + * Normally, it won't be necessary to access this file directly; you can call + * read_attr_data() or write_attr_data() to get at the data stored in this + * file. (And read_attr_data() is called automatically when the Flt file is + * read in.) + */ +Filename FltTexture:: +get_attr_filename() const { + std::string texture_filename = get_texture_filename(); + return Filename::binary_filename(texture_filename + ".attr"); +} + +/** + * Opens up the texture's .attr file and reads its data into the extra + * FltTexture fields. This is normally performed automatically when the Flt + * file is read from disk. + */ +FltError FltTexture:: +read_attr_data() { + Filename attr_filename = get_attr_filename(); + + std::ifstream attr; + if (!attr_filename.open_read(attr)) { + return FE_could_not_open; + } + + // Determine the file's size so we can read it all into one big datagram. + attr.seekg(0, std::ios::end); + if (attr.fail()) { + return FE_read_error; + } + std::streampos length = attr.tellg(); + + char *buffer = new char[length]; + + attr.seekg(0, std::ios::beg); + attr.read(buffer, length); + if (attr.fail()) { + return FE_read_error; + } + + Datagram datagram(buffer, length); + delete[] buffer; + + return unpack_attr(datagram); +} + +/** + * Writes the texture's .attr file. This may or may not be performed + * automatically, according to the setting of + * FltHeader::set_auto_attr_update(). + */ +FltError FltTexture:: +write_attr_data() const { + return write_attr_data(get_attr_filename()); +} + +/** + * Writes the texture's .attr file to the named file. + */ +FltError FltTexture:: +write_attr_data(Filename attr_filename) const { + Datagram datagram; + FltError result = pack_attr(datagram); + if (result != FE_ok) { + return result; + } + + attr_filename.set_binary(); + std::ofstream attr; + if (!attr_filename.open_write(attr)) { + return FE_could_not_open; + } + + attr.write((const char *)datagram.get_data(), datagram.get_length()); + if (attr.fail()) { + return FE_write_error; + } + return FE_ok; +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTexture:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_texture, false); + DatagramIterator &iterator = reader.get_iterator(); + + if (_header->get_flt_version() < 1420) { + _orig_filename = iterator.get_fixed_string(80); + } else { + _orig_filename = iterator.get_fixed_string(200); + } + _converted_filename = _header->convert_path(Filename::from_os_specific(_orig_filename), get_model_path()); + _pattern_index = iterator.get_be_int32(); + _x_location = iterator.get_be_int32(); + _y_location = iterator.get_be_int32(); + + if (read_attr_data() != FE_ok) { + nout << "Unable to read attribute file " << get_attr_filename() << "\n"; + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTexture:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_texture); + Datagram &datagram = writer.update_datagram(); + + datagram.add_fixed_string(_orig_filename, 200); + datagram.add_be_int32(_pattern_index); + datagram.add_be_int32(_x_location); + datagram.add_be_int32(_y_location); + + if (_header->get_auto_attr_update() == FltHeader::AU_always || + (_header->get_auto_attr_update() == FltHeader::AU_if_missing && + !get_attr_filename().exists())) { + if (write_attr_data() != FE_ok) { + nout << "Unable to write attribute file " << get_attr_filename() << "\n"; + } + } + + return true; +} + +/** + * Reads the data from the attribute file. + */ +FltError FltTexture:: +unpack_attr(const Datagram &datagram) { + DatagramIterator iterator(datagram); + + _num_texels_u = iterator.get_be_int32(); + _num_texels_v = iterator.get_be_int32(); + _real_world_size_u = iterator.get_be_int32(); + _real_world_size_v = iterator.get_be_int32(); + _up_vector_x = iterator.get_be_int32(); + _up_vector_y = iterator.get_be_int32(); + _file_format = (FileFormat)iterator.get_be_int32(); + _min_filter = (Minification)iterator.get_be_int32(); + _mag_filter = (Magnification)iterator.get_be_int32(); + _repeat = (RepeatType)iterator.get_be_int32(); + _repeat_u = (RepeatType)iterator.get_be_int32(); + _repeat_v = (RepeatType)iterator.get_be_int32(); + _modify_flag = iterator.get_be_int32(); + _x_pivot_point = iterator.get_be_int32(); + _y_pivot_point = iterator.get_be_int32(); + _env_type = (EnvironmentType)iterator.get_be_int32(); + _intensity_is_alpha = (iterator.get_be_int32() != 0); + iterator.skip_bytes(4 * 8); + iterator.skip_bytes(4); // Undocumented padding. + _float_real_world_size_u = iterator.get_be_float64(); + _float_real_world_size_v = iterator.get_be_float64(); + _imported_origin_code = iterator.get_be_int32(); + _kernel_version = iterator.get_be_int32(); + _internal_format = (InternalFormat)iterator.get_be_int32(); + _external_format = (ExternalFormat)iterator.get_be_int32(); + _use_mipmap_kernel = (iterator.get_be_int32() != 0); + int i; + for (i = 0; i < 8; i++) { + _mipmap_kernel[i] = iterator.get_be_float32(); + } + _use_lod_scale = (iterator.get_be_int32() != 0); + for (i = 0; i < 8; i++) { + _lod_scale[i]._lod = iterator.get_be_float32(); + _lod_scale[i]._scale = iterator.get_be_float32(); + } + _clamp = iterator.get_be_float32(); + _mag_filter_alpha = (Magnification)iterator.get_be_int32(); + _mag_filter_color = (Magnification)iterator.get_be_int32(); + iterator.skip_bytes(4 + 4 * 8); + _lambert_conic_central_meridian = iterator.get_be_float64(); + _lambert_conic_upper_latitude = iterator.get_be_float64(); + _lambert_conic_lower_latitude = iterator.get_be_float64(); + iterator.skip_bytes(8 + 4 * 5); + _use_detail = (iterator.get_be_int32() != 0); + _detail_j = iterator.get_be_int32(); + _detail_k = iterator.get_be_int32(); + _detail_m = iterator.get_be_int32(); + _detail_n = iterator.get_be_int32(); + _detail_scramble = iterator.get_be_int32(); + _use_tile = (iterator.get_be_int32() != 0); + _tile_lower_left_u = iterator.get_be_float32(); + _tile_lower_left_v = iterator.get_be_float32(); + _tile_upper_right_u = iterator.get_be_float32(); + _tile_upper_right_v = iterator.get_be_float32(); + _projection = (ProjectionType)iterator.get_be_int32(); + _earth_model = (EarthModel)iterator.get_be_int32(); + iterator.skip_bytes(4); + _utm_zone = iterator.get_be_int32(); + _image_origin = (ImageOrigin)iterator.get_be_int32(); + _geospecific_points_units = (PointsUnits)iterator.get_be_int32(); + _geospecific_hemisphere = (Hemisphere)iterator.get_be_int32(); + iterator.skip_bytes(4 + 4 + 149 * 4); + iterator.skip_bytes(8); // Undocumented padding. + _comment = iterator.get_fixed_string(512); + + if (iterator.get_remaining_size() != 0) { + iterator.skip_bytes(13 * 4); + iterator.skip_bytes(4); // Undocumented padding. + _file_version = iterator.get_be_int32(); + + // Now read the geospecific control points. + _geospecific_control_points.clear(); + int num_points = iterator.get_be_int32(); + if (num_points > 0) { + iterator.skip_bytes(4); + + while (num_points > 0) { + GeospecificControlPoint gcp; + gcp._uv[0] = iterator.get_be_float64(); + gcp._uv[1] = iterator.get_be_float64(); + gcp._real_earth[0] = iterator.get_be_float64(); + gcp._real_earth[1] = iterator.get_be_float64(); + } + } + + if (iterator.get_remaining_size() != 0) { + int num_defs = iterator.get_be_int32(); + while (num_defs > 0) { + SubtextureDef def; + def._name = iterator.get_fixed_string(32); + def._left = iterator.get_be_int32(); + def._bottom = iterator.get_be_int32(); + def._right = iterator.get_be_int32(); + def._top = iterator.get_be_int32(); + } + } + } + + check_remaining_size(iterator); + return FE_ok; +} + +/** + * Packs the attribute data into a big datagram. + */ +FltError FltTexture:: +pack_attr(Datagram &datagram) const { + datagram.add_be_int32(_num_texels_u); + datagram.add_be_int32(_num_texels_v); + datagram.add_be_int32(_real_world_size_u); + datagram.add_be_int32(_real_world_size_v); + datagram.add_be_int32(_up_vector_x); + datagram.add_be_int32(_up_vector_y); + datagram.add_be_int32(_file_format); + datagram.add_be_int32(_min_filter); + datagram.add_be_int32(_mag_filter); + datagram.add_be_int32(_repeat); + datagram.add_be_int32(_repeat_u); + datagram.add_be_int32(_repeat_v); + datagram.add_be_int32(_modify_flag); + datagram.add_be_int32(_x_pivot_point); + datagram.add_be_int32(_y_pivot_point); + datagram.add_be_int32(_env_type); + datagram.add_be_int32(_intensity_is_alpha); + datagram.pad_bytes(4 * 8); + datagram.pad_bytes(4); // Undocumented padding. + datagram.add_be_float64(_float_real_world_size_u); + datagram.add_be_float64(_float_real_world_size_v); + datagram.add_be_int32(_imported_origin_code); + datagram.add_be_int32(_kernel_version); + datagram.add_be_int32(_internal_format); + datagram.add_be_int32(_external_format); + datagram.add_be_int32(_use_mipmap_kernel); + int i; + for (i = 0; i < 8; i++) { + datagram.add_be_float32(_mipmap_kernel[i]); + } + datagram.add_be_int32(_use_lod_scale); + for (i = 0; i < 8; i++) { + datagram.add_be_float32(_lod_scale[i]._lod); + datagram.add_be_float32(_lod_scale[i]._scale); + } + datagram.add_be_float32(_clamp); + datagram.add_be_int32(_mag_filter_alpha); + datagram.add_be_int32(_mag_filter_color); + datagram.pad_bytes(4 + 4 * 8); + datagram.add_be_float64(_lambert_conic_central_meridian); + datagram.add_be_float64(_lambert_conic_upper_latitude); + datagram.add_be_float64(_lambert_conic_lower_latitude); + datagram.pad_bytes(8 + 4 * 5); + datagram.add_be_int32(_use_detail); + datagram.add_be_int32(_detail_j); + datagram.add_be_int32(_detail_k); + datagram.add_be_int32(_detail_m); + datagram.add_be_int32(_detail_n); + datagram.add_be_int32(_detail_scramble); + datagram.add_be_int32(_use_tile); + datagram.add_be_float32(_tile_lower_left_u); + datagram.add_be_float32(_tile_lower_left_v); + datagram.add_be_float32(_tile_upper_right_u); + datagram.add_be_float32(_tile_upper_right_v); + datagram.add_be_int32(_projection); + datagram.add_be_int32(_earth_model); + datagram.pad_bytes(4); + datagram.add_be_int32(_utm_zone); + datagram.add_be_int32(_image_origin); + datagram.add_be_int32(_geospecific_points_units); + datagram.add_be_int32(_geospecific_hemisphere); + datagram.pad_bytes(4 + 4 + 149 * 4); + datagram.pad_bytes(8); // Undocumented padding. + datagram.add_fixed_string(_comment, 512); + datagram.pad_bytes(13 * 4); + datagram.pad_bytes(4); // Undocumented padding. + datagram.add_be_int32(_file_version); + + // Now write the geospecific control points. + datagram.add_be_int32(_geospecific_control_points.size()); + if (!_geospecific_control_points.empty()) { + datagram.pad_bytes(4); + GeospecificControlPoints::const_iterator pi; + for (pi = _geospecific_control_points.begin(); + pi != _geospecific_control_points.end(); + ++pi) { + const GeospecificControlPoint &gcp = (*pi); + datagram.add_be_float64(gcp._uv[0]); + datagram.add_be_float64(gcp._uv[1]); + datagram.add_be_float64(gcp._real_earth[0]); + datagram.add_be_float64(gcp._real_earth[1]); + } + } + + // Also write out the subtexture definitions. + datagram.add_be_int32(_subtexture_defs.size()); + SubtextureDefs::const_iterator di; + for (di = _subtexture_defs.begin(); + di != _subtexture_defs.end(); + ++di) { + const SubtextureDef &def = (*di); + datagram.add_fixed_string(def._name, 31); + datagram.add_int8(0); + datagram.add_be_int32(def._left); + datagram.add_be_int32(def._bottom); + datagram.add_be_int32(def._right); + datagram.add_be_int32(def._top); + } + + return FE_ok; +} diff --git a/pandatool/src/flt/fltTexture.h b/pandatool/src/flt/fltTexture.h new file mode 100644 index 00000000..34a487e1 --- /dev/null +++ b/pandatool/src/flt/fltTexture.h @@ -0,0 +1,252 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTexture.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTTEXTURE_H +#define FLTTEXTURE_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "filename.h" +#include "luse.h" + +/** + * Represents a single texture in the texture palette. + */ +class FltTexture : public FltRecord { +public: + FltTexture(FltHeader *header); + + virtual void apply_converted_filenames(); + + std::string _orig_filename; + Filename _converted_filename; + int _pattern_index; + int _x_location; + int _y_location; + + Filename get_texture_filename() const; + void set_texture_filename(const Filename &filename); + Filename get_attr_filename() const; + FltError read_attr_data(); + FltError write_attr_data() const; + FltError write_attr_data(Filename attr_filename) const; + + // The remaining fields are from the attr file. + enum FileFormat { + FF_none = -1, + FF_att_8_pattern = 0, + FF_att_8_template = 1, + FF_sgi_i = 2, + FF_sgi_ia = 3, + FF_sgi_rgb = 4, + FF_sgi_rgba = 5 + }; + + enum Minification { + MN_point = 0, + MN_bilinear = 1, + MN_OB_mipmap = 2, // obsolete + MN_mipmap_point = 3, + MN_mipmap_linear = 4, + MN_mipmap_bilinear = 5, + MN_mipmap_trilinear = 6, + MN_bicubic = 8, + MN_bilinear_gequal = 9, + MN_bilinear_lequal = 10, + MN_bicubic_gequal = 11, + MN_bicubic_lequal = 12 + }; + + enum Magnification { + MG_point = 0, + MG_bilinear = 1, + MG_bicubic = 3, + MG_sharpen = 4, + MG_add_detail = 5, + MG_modulate_detail = 6, + MG_bilinear_gequal = 7, + MG_bilinear_lequal = 8, + MG_bicubic_gequal = 9, + MG_bicubic_lequal = 10 + }; + + enum RepeatType { + RT_repeat = 0, + RT_clamp = 1 + }; + + enum EnvironmentType { + ET_modulate = 0, + ET_blend = 1, + ET_decal = 2, + ET_color = 3 + }; + + enum InternalFormat { + IF_default = 0, + IF_i_12a_4 = 1, + IF_ia_8 = 2, + IF_rgb_5 = 3, + IF_rgba_4 = 4, + IF_ia_12 = 5, + IF_rgba_8 = 6, + IF_rgba_12 = 7, + IF_i_16 = 8, // shadow mode only + IF_rgb_12 = 9 + }; + + enum ExternalFormat { + EF_default = 0, + EF_pack_8 = 1, + EF_pack_16 = 2 + }; + + enum ProjectionType { + PT_flat_earth = 0, + PT_lambert = 3, + PT_utm = 4, + PT_undefined = 7 + }; + + enum EarthModel { + EM_wgs84 = 0, + EM_wgs72 = 1, + EM_bessel = 2, + EM_clarke_1866 = 3, + EM_nad27 = 4 + }; + + enum ImageOrigin { + IO_lower_left = 0, + IO_upper_left = 1 + }; + + enum PointsUnits { + PU_degrees = 0, + PU_meters = 1, + PU_pixels = 2 + }; + + enum Hemisphere { + H_southern = 0, + H_northern = 1, + }; + + struct LODScale { + PN_stdfloat _lod; + PN_stdfloat _scale; + }; + + struct GeospecificControlPoint { + LPoint2d _uv; + LPoint2d _real_earth; + }; + + typedef pvector GeospecificControlPoints; + + struct SubtextureDef { + std::string _name; + int _left; + int _bottom; + int _right; + int _top; + }; + typedef pvector SubtextureDefs; + + int _num_texels_u; + int _num_texels_v; + int _real_world_size_u; + int _real_world_size_v; + int _up_vector_x; + int _up_vector_y; + FileFormat _file_format; + Minification _min_filter; + Magnification _mag_filter; + RepeatType _repeat; + RepeatType _repeat_u; + RepeatType _repeat_v; + int _modify_flag; + int _x_pivot_point; + int _y_pivot_point; + EnvironmentType _env_type; + bool _intensity_is_alpha; // if true, a one-channel image is actually + // an alpha image, not an intensity image. + double _float_real_world_size_u; + double _float_real_world_size_v; + int _imported_origin_code; + int _kernel_version; + InternalFormat _internal_format; + ExternalFormat _external_format; + bool _use_mipmap_kernel; + PN_stdfloat _mipmap_kernel[8]; + bool _use_lod_scale; + LODScale _lod_scale[8]; + PN_stdfloat _clamp; + Magnification _mag_filter_alpha; + Magnification _mag_filter_color; + double _lambert_conic_central_meridian; + double _lambert_conic_upper_latitude; + double _lambert_conic_lower_latitude; + bool _use_detail; + int _detail_j; + int _detail_k; + int _detail_m; + int _detail_n; + int _detail_scramble; + bool _use_tile; + PN_stdfloat _tile_lower_left_u; + PN_stdfloat _tile_lower_left_v; + PN_stdfloat _tile_upper_right_u; + PN_stdfloat _tile_upper_right_v; + ProjectionType _projection; + EarthModel _earth_model; + int _utm_zone; + ImageOrigin _image_origin; + PointsUnits _geospecific_points_units; + Hemisphere _geospecific_hemisphere; + std::string _comment; + int _file_version; + GeospecificControlPoints _geospecific_control_points; + SubtextureDefs _subtexture_defs; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + FltError unpack_attr(const Datagram &datagram); + FltError pack_attr(Datagram &datagram) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltTexture", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif diff --git a/pandatool/src/flt/fltTrackplane.cxx b/pandatool/src/flt/fltTrackplane.cxx new file mode 100644 index 00000000..13f8a58c --- /dev/null +++ b/pandatool/src/flt/fltTrackplane.cxx @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTrackplane.cxx + * @author drose + * @date 2000-08-26 + */ + +#include "fltTrackplane.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +/** + * + */ +FltTrackplane:: +FltTrackplane() { + _origin.set(0.0, 0.0, 0.0); + _alignment.set(0.0, 0.0, 0.0); + _plane.set(0.0, 0.0, 1.0); + _grid_state = false; + _grid_under = false; + _grid_angle = 0.0; + _grid_spacing_x = 1; + _grid_spacing_y = 1; + _snap_to_grid = false; + _grid_size = 10.0; + _grid_spacing_direction = 0; + _grid_mask = 0; +} + +/** + * + */ +bool FltTrackplane:: +extract_record(FltRecordReader &reader) { + DatagramIterator &iterator = reader.get_iterator(); + + _origin[0] = iterator.get_be_float64(); + _origin[1] = iterator.get_be_float64(); + _origin[2] = iterator.get_be_float64(); + _alignment[0] = iterator.get_be_float64(); + _alignment[1] = iterator.get_be_float64(); + _alignment[0] = iterator.get_be_float64(); + _plane[0] = iterator.get_be_float64(); + _plane[1] = iterator.get_be_float64(); + _plane[2] = iterator.get_be_float64(); + _grid_state = (iterator.get_be_int32() != 0); + _grid_under = (iterator.get_be_int32() != 0); + _grid_angle = iterator.get_be_float32(); + iterator.skip_bytes(4); + _grid_spacing_x = iterator.get_be_float64(); + _grid_spacing_y = iterator.get_be_float64(); + _snap_to_grid = (iterator.get_be_int32() != 0); + _grid_size = iterator.get_be_float64(); + _grid_spacing_direction = iterator.get_be_int32(); + _grid_mask = iterator.get_be_int32(); + iterator.skip_bytes(4); + + return true; +} + +/** + * + */ +bool FltTrackplane:: +build_record(FltRecordWriter &writer) const { + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_float64(_origin[0]); + datagram.add_be_float64(_origin[1]); + datagram.add_be_float64(_origin[2]); + datagram.add_be_float64(_alignment[0]); + datagram.add_be_float64(_alignment[1]); + datagram.add_be_float64(_alignment[2]); + datagram.add_be_float64(_plane[0]); + datagram.add_be_float64(_plane[1]); + datagram.add_be_float64(_plane[2]); + datagram.add_be_int32(_grid_state); + datagram.add_be_int32(_grid_under); + datagram.add_be_float32(_grid_angle); + datagram.pad_bytes(4); + datagram.add_be_float64(_grid_spacing_x); + datagram.add_be_float64(_grid_spacing_y); + datagram.add_be_int32(_snap_to_grid); + datagram.add_be_float64(_grid_size); + datagram.add_be_int32(_grid_spacing_direction); + datagram.add_be_int32(_grid_mask); + datagram.pad_bytes(4); + + return true; +} diff --git a/pandatool/src/flt/fltTrackplane.h b/pandatool/src/flt/fltTrackplane.h new file mode 100644 index 00000000..306f81e9 --- /dev/null +++ b/pandatool/src/flt/fltTrackplane.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTrackplane.h + * @author drose + * @date 2000-08-26 + */ + +#ifndef FLTTRACKPLANE_H +#define FLTTRACKPLANE_H + +#include "pandatoolbase.h" + +#include "luse.h" + +class FltRecordReader; +class FltRecordWriter; + +/** + * A single trackplane entry in the eyepoint/trackplane palette. + */ +class FltTrackplane { +public: + FltTrackplane(); + + bool extract_record(FltRecordReader &reader); + bool build_record(FltRecordWriter &writer) const; + +public: + LPoint3d _origin; + LPoint3d _alignment; + LVector3d _plane; + bool _grid_state; + bool _grid_under; + PN_stdfloat _grid_angle; + double _grid_spacing_x; + double _grid_spacing_y; + bool _snap_to_grid; + double _grid_size; + int _grid_spacing_direction; + int _grid_mask; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformGeneralMatrix.cxx b/pandatool/src/flt/fltTransformGeneralMatrix.cxx new file mode 100644 index 00000000..1c8adf4c --- /dev/null +++ b/pandatool/src/flt/fltTransformGeneralMatrix.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformGeneralMatrix.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltTransformGeneralMatrix.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformGeneralMatrix::_type_handle; + +/** + * + */ +FltTransformGeneralMatrix:: +FltTransformGeneralMatrix(FltHeader *header) : FltTransformRecord(header) { +} + +/** + * Directly sets the general matrix. + */ +void FltTransformGeneralMatrix:: +set_matrix(const LMatrix4d &matrix) { + _matrix = matrix; +} + +/** + * Directly sets the general matrix. + */ +void FltTransformGeneralMatrix:: +set_matrix(const LMatrix4f &matrix) { + _matrix = LCAST(double, matrix); +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformGeneralMatrix:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_general_matrix, false); + DatagramIterator &iterator = reader.get_iterator(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + _matrix(r, c) = iterator.get_be_float32(); + } + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformGeneralMatrix:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_general_matrix); + Datagram &datagram = writer.update_datagram(); + + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + datagram.add_be_float32(_matrix(r, c)); + } + } + + return true; +} diff --git a/pandatool/src/flt/fltTransformGeneralMatrix.h b/pandatool/src/flt/fltTransformGeneralMatrix.h new file mode 100644 index 00000000..e56fe94e --- /dev/null +++ b/pandatool/src/flt/fltTransformGeneralMatrix.h @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformGeneralMatrix.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTTRANSFORMGENERALMATRIX_H +#define FLTTRANSFORMGENERALMATRIX_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A general 4x4 matrix. This appears in the flt file when there is no record + * of the composition of the transform. + */ +class FltTransformGeneralMatrix : public FltTransformRecord { +public: + FltTransformGeneralMatrix(FltHeader *header); + + void set_matrix(const LMatrix4d &matrix); + void set_matrix(const LMatrix4f &matrix); + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformGeneralMatrix", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformPut.cxx b/pandatool/src/flt/fltTransformPut.cxx new file mode 100644 index 00000000..612c6b07 --- /dev/null +++ b/pandatool/src/flt/fltTransformPut.cxx @@ -0,0 +1,195 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformPut.cxx + * @author drose + * @date 2000-08-29 + */ + +#include "fltTransformPut.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +#include "look_at.h" + +TypeHandle FltTransformPut::_type_handle; + +/** + * + */ +FltTransformPut:: +FltTransformPut(FltHeader *header) : FltTransformRecord(header) { + _from_origin.set(0.0, 0.0, 0.0); + _from_align.set(1.0, 0.0, 0.0); + _from_track.set(1.0, 0.0, 0.0); + _to_origin.set(0.0, 0.0, 0.0); + _to_align.set(1.0, 0.0, 0.0); + _to_track.set(1.0, 0.0, 0.0); +} + +/** + * Defines the put explicitly. The transformation will map the three "from" + * points to the corresponding three "to" points. + */ +void FltTransformPut:: +set(const LPoint3d &from_origin, const LPoint3d &from_align, + const LPoint3d &from_track, + const LPoint3d &to_origin, const LPoint3d &to_align, + const LPoint3d &to_track) { + _from_origin = from_origin; + _from_align = from_align; + _from_track = from_track; + _to_origin = to_origin; + _to_align = to_align; + _to_track = to_track; + + recompute_matrix(); +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_from_origin() const { + return _from_origin; +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_from_align() const { + return _from_align; +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_from_track() const { + return _from_track; +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_to_origin() const { + return _to_origin; +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_to_align() const { + return _to_align; +} + +/** + * + */ +const LPoint3d &FltTransformPut:: +get_to_track() const { + return _to_track; +} + +/** + * + */ +void FltTransformPut:: +recompute_matrix() { + LMatrix4d r1, r2; + look_at(r1, _from_align - _from_origin, _from_track - _from_origin, CS_zup_right); + look_at(r2, _to_align - _to_origin, _to_track - _to_origin, CS_zup_right); + + _matrix = + LMatrix4d::translate_mat(-_from_origin) * + invert(r1) * + r2 * + LMatrix4d::translate_mat(_to_origin); +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformPut:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_put, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _from_origin[0] = iterator.get_be_float64(); + _from_origin[1] = iterator.get_be_float64(); + _from_origin[2] = iterator.get_be_float64(); + _from_align[0] = iterator.get_be_float64(); + _from_align[1] = iterator.get_be_float64(); + _from_align[2] = iterator.get_be_float64(); + _from_track[0] = iterator.get_be_float64(); + _from_track[1] = iterator.get_be_float64(); + _from_track[2] = iterator.get_be_float64(); + _to_origin[0] = iterator.get_be_float64(); + _to_origin[1] = iterator.get_be_float64(); + _to_origin[2] = iterator.get_be_float64(); + _to_align[0] = iterator.get_be_float64(); + _to_align[1] = iterator.get_be_float64(); + _to_align[2] = iterator.get_be_float64(); + _to_track[0] = iterator.get_be_float64(); + _to_track[1] = iterator.get_be_float64(); + _to_track[2] = iterator.get_be_float64(); + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformPut:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_put); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_from_origin[0]); + datagram.add_be_float64(_from_origin[1]); + datagram.add_be_float64(_from_origin[2]); + datagram.add_be_float64(_from_align[0]); + datagram.add_be_float64(_from_align[1]); + datagram.add_be_float64(_from_align[2]); + datagram.add_be_float64(_from_track[0]); + datagram.add_be_float64(_from_track[1]); + datagram.add_be_float64(_from_track[2]); + datagram.add_be_float64(_to_origin[0]); + datagram.add_be_float64(_to_origin[1]); + datagram.add_be_float64(_to_origin[2]); + datagram.add_be_float64(_to_align[0]); + datagram.add_be_float64(_to_align[1]); + datagram.add_be_float64(_to_align[2]); + datagram.add_be_float64(_to_track[0]); + datagram.add_be_float64(_to_track[1]); + datagram.add_be_float64(_to_track[2]); + + return true; +} diff --git a/pandatool/src/flt/fltTransformPut.h b/pandatool/src/flt/fltTransformPut.h new file mode 100644 index 00000000..fdfab778 --- /dev/null +++ b/pandatool/src/flt/fltTransformPut.h @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformPut.h + * @author drose + * @date 2000-08-29 + */ + +#ifndef FLTTRANSFORMPUT_H +#define FLTTRANSFORMPUT_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A "put", which is a MultiGen concept of defining a transformation by + * mapping three arbitrary points to three new arbitrary points. + */ +class FltTransformPut : public FltTransformRecord { +public: + FltTransformPut(FltHeader *header); + + void set(const LPoint3d &from_origin, + const LPoint3d &from_align, + const LPoint3d &from_track, + const LPoint3d &to_origin, + const LPoint3d &to_align, + const LPoint3d &to_track); + + const LPoint3d &get_from_origin() const; + const LPoint3d &get_from_align() const; + const LPoint3d &get_from_track() const; + const LPoint3d &get_to_origin() const; + const LPoint3d &get_to_align() const; + const LPoint3d &get_to_track() const; + +private: + void recompute_matrix(); + + LPoint3d _from_origin; + LPoint3d _from_align; + LPoint3d _from_track; + LPoint3d _to_origin; + LPoint3d _to_align; + LPoint3d _to_track; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformPut", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRecord.cxx b/pandatool/src/flt/fltTransformRecord.cxx new file mode 100644 index 00000000..bc1275bc --- /dev/null +++ b/pandatool/src/flt/fltTransformRecord.cxx @@ -0,0 +1,33 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRecord.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltTransformRecord.h" + +TypeHandle FltTransformRecord::_type_handle; + +/** + * + */ +FltTransformRecord:: +FltTransformRecord(FltHeader *header) : FltRecord(header) { + _matrix = LMatrix4d::ident_mat(); +} + +/** + * Returns the transform matrix represented by this particular component of + * the transform. + */ +const LMatrix4d &FltTransformRecord:: +get_matrix() const { + return _matrix; +} diff --git a/pandatool/src/flt/fltTransformRecord.h b/pandatool/src/flt/fltTransformRecord.h new file mode 100644 index 00000000..b2679b84 --- /dev/null +++ b/pandatool/src/flt/fltTransformRecord.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRecord.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTTRANSFORMRECORD_H +#define FLTTRANSFORMRECORD_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "luse.h" + +/** + * A base class for a number of types of ancillary records that follow beads + * and indicate some kind of a transformation. Pointers of this type are + * collected in the FltTransformation class. + */ +class FltTransformRecord : public FltRecord { +public: + FltTransformRecord(FltHeader *header); + + const LMatrix4d &get_matrix() const; + +protected: + LMatrix4d _matrix; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltTransformRecord", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltBead; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRotateAboutEdge.cxx b/pandatool/src/flt/fltTransformRotateAboutEdge.cxx new file mode 100644 index 00000000..ec4bbf95 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutEdge.cxx @@ -0,0 +1,144 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateAboutEdge.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltTransformRotateAboutEdge.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformRotateAboutEdge::_type_handle; + +/** + * + */ +FltTransformRotateAboutEdge:: +FltTransformRotateAboutEdge(FltHeader *header) : FltTransformRecord(header) { + _point_a.set(0.0, 0.0, 0.0); + _point_b.set(1.0, 0.0, 0.0); + _angle = 0.0; +} + +/** + * Defines the rotation. The angle is given in degrees, counterclockwise + * about the axis as seen from point a. + */ +void FltTransformRotateAboutEdge:: +set(const LPoint3d &point_a, const LPoint3d &point_b, PN_stdfloat angle) { + _point_a = point_a; + _point_b = point_b; + _angle = angle; + + recompute_matrix(); +} + +/** + * + */ +const LPoint3d &FltTransformRotateAboutEdge:: +get_point_a() const { + return _point_a; +} + +/** + * + */ +const LPoint3d &FltTransformRotateAboutEdge:: +get_point_b() const { + return _point_b; +} + +/** + * Returns the angle of rotation, in degrees counterclockwise about the axis + * as seen from point a. + */ +PN_stdfloat FltTransformRotateAboutEdge:: +get_angle() const { + return _angle; +} + +/** + * + */ +void FltTransformRotateAboutEdge:: +recompute_matrix() { + if (_point_a == _point_b) { + // Degenerate case. + _matrix = LMatrix4d::ident_mat(); + } else { + LVector3d axis = _point_b - _point_a; + _matrix = + LMatrix4d::translate_mat(-_point_a) * + LMatrix4d::rotate_mat(_angle, normalize(axis), CS_zup_right) * + LMatrix4d::translate_mat(_point_a); + } +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformRotateAboutEdge:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_about_edge, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _point_a[0] = iterator.get_be_float64(); + _point_a[1] = iterator.get_be_float64(); + _point_a[2] = iterator.get_be_float64(); + _point_b[0] = iterator.get_be_float64(); + _point_b[1] = iterator.get_be_float64(); + _point_b[2] = iterator.get_be_float64(); + _angle = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformRotateAboutEdge:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_rotate_about_edge); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_point_a[0]); + datagram.add_be_float64(_point_a[1]); + datagram.add_be_float64(_point_a[2]); + datagram.add_be_float64(_point_b[0]); + datagram.add_be_float64(_point_b[1]); + datagram.add_be_float64(_point_b[2]); + datagram.add_be_float32(_angle); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltTransformRotateAboutEdge.h b/pandatool/src/flt/fltTransformRotateAboutEdge.h new file mode 100644 index 00000000..34ce5854 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutEdge.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateAboutEdge.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTTRANSFORMROTATEABOUTEDGE_H +#define FLTTRANSFORMROTATEABOUTEDGE_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A transformation that rotates about a particular axis in space, defined by + * two endpoints. + */ +class FltTransformRotateAboutEdge : public FltTransformRecord { +public: + FltTransformRotateAboutEdge(FltHeader *header); + + void set(const LPoint3d &point_a, const LPoint3d &point_b, PN_stdfloat angle); + + const LPoint3d &get_point_a() const; + const LPoint3d &get_point_b() const; + PN_stdfloat get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _point_a; + LPoint3d _point_b; + PN_stdfloat _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateAboutEdge", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRotateAboutPoint.cxx b/pandatool/src/flt/fltTransformRotateAboutPoint.cxx new file mode 100644 index 00000000..1c6db879 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutPoint.cxx @@ -0,0 +1,140 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateAboutPoint.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltTransformRotateAboutPoint.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformRotateAboutPoint::_type_handle; + +/** + * + */ +FltTransformRotateAboutPoint:: +FltTransformRotateAboutPoint(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _axis.set(1.0, 0.0, 0.0); + _angle = 0.0; +} + +/** + * Defines the rotation. The angle is given in degrees, counterclockwise + * about the axis as seen from point a. + */ +void FltTransformRotateAboutPoint:: +set(const LPoint3d ¢er, const LVector3 &axis, PN_stdfloat angle) { + _center = center; + _axis = axis; + _angle = angle; + + recompute_matrix(); +} + +/** + * + */ +const LPoint3d &FltTransformRotateAboutPoint:: +get_center() const { + return _center; +} + +/** + * + */ +const LVector3 &FltTransformRotateAboutPoint:: +get_axis() const { + return _axis; +} + +/** + * Returns the angle of rotation, in degrees counterclockwise about the axis. + */ +PN_stdfloat FltTransformRotateAboutPoint:: +get_angle() const { + return _angle; +} + +/** + * + */ +void FltTransformRotateAboutPoint:: +recompute_matrix() { + if (_axis == LVector3::zero()) { + // Degenerate case. + _matrix = LMatrix4d::ident_mat(); + } else { + LVector3d axis = LCAST(double, _axis); + + _matrix = + LMatrix4d::translate_mat(-_center) * + LMatrix4d::rotate_mat(_angle, axis, CS_zup_right) * + LMatrix4d::translate_mat(_center); + } +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformRotateAboutPoint:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_about_point, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _axis[0] = iterator.get_be_float32(); + _axis[1] = iterator.get_be_float32(); + _axis[2] = iterator.get_be_float32(); + _angle = iterator.get_be_float32(); + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformRotateAboutPoint:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_rotate_about_point); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float32(_axis[0]); + datagram.add_be_float32(_axis[1]); + datagram.add_be_float32(_axis[2]); + datagram.add_be_float32(_angle); + + return true; +} diff --git a/pandatool/src/flt/fltTransformRotateAboutPoint.h b/pandatool/src/flt/fltTransformRotateAboutPoint.h new file mode 100644 index 00000000..716bb56c --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateAboutPoint.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateAboutPoint.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTTRANSFORMROTATEABOUTPOINT_H +#define FLTTRANSFORMROTATEABOUTPOINT_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A transformation that rotates about a particular axis in space, defined by + * a point and vector. + */ +class FltTransformRotateAboutPoint : public FltTransformRecord { +public: + FltTransformRotateAboutPoint(FltHeader *header); + + void set(const LPoint3d ¢er, const LVector3 &axis, PN_stdfloat angle); + + const LPoint3d &get_center() const; + const LVector3 &get_axis() const; + PN_stdfloat get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LVector3 _axis; + PN_stdfloat _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateAboutPoint", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformRotateScale.cxx b/pandatool/src/flt/fltTransformRotateScale.cxx new file mode 100644 index 00000000..e81dbb47 --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateScale.cxx @@ -0,0 +1,208 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateScale.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltTransformRotateScale.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +#include "mathNumbers.h" +#include "look_at.h" + +TypeHandle FltTransformRotateScale::_type_handle; + +/** + * + */ +FltTransformRotateScale:: +FltTransformRotateScale(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _reference_point.set(0.0, 0.0, 0.0); + _to_point.set(0.0, 0.0, 0.0); + _overall_scale = 1.0; + _axis_scale = 1.0; + _angle = 0.0; +} + +/** + * Defines the transform explicitly. The angle of rotation is determined by + * the angle between the reference point and the to point (relative to the + * center), and the scale factor is determined by the distance between the + * reference point and the center point. If axis_scale is true, the scale is + * along reference point axis only; otherwise, it is a uniform scale. + */ +void FltTransformRotateScale:: +set(const LPoint3d ¢er, const LPoint3d &reference_point, + const LPoint3d &to_point, bool axis_scale) { + _center = center; + _reference_point = reference_point; + _to_point = to_point; + + LVector3d v1 = _reference_point - _center; + LVector3d v2 = _to_point - _center; + + _angle = + acos(dot(normalize(v1), normalize(v2))) * 180.0 / MathNumbers::pi; + + if (axis_scale) { + _axis_scale = length(v1); + _overall_scale = 1.0; + } else { + _overall_scale = length(v1); + _axis_scale = 1.0; + } + + recompute_matrix(); +} + +/** + * + */ +const LPoint3d &FltTransformRotateScale:: +get_center() const { + return _center; +} + +/** + * + */ +const LPoint3d &FltTransformRotateScale:: +get_reference_point() const { + return _reference_point; +} + +/** + * + */ +const LPoint3d &FltTransformRotateScale:: +get_to_point() const { + return _to_point; +} + +/** + * Returns the overall scale factor. + */ +PN_stdfloat FltTransformRotateScale:: +get_overall_scale() const { + return _overall_scale; +} + +/** + * Returns the scale factor in the direction of the axis. + */ +PN_stdfloat FltTransformRotateScale:: +get_axis_scale() const { + return _axis_scale; +} + +/** + * Returns the angle of rotation in degrees. + */ +PN_stdfloat FltTransformRotateScale:: +get_angle() const { + return _angle; +} + +/** + * + */ +void FltTransformRotateScale:: +recompute_matrix() { + LVector3d v1 = _reference_point - _center; + LVector3d v2 = _to_point - _center; + LVector3d rotate_axis = normalize(cross(v1, v2)); + + // To scale along an axis, we have to do a bit of work. First determine the + // matrices to rotate and unrotate the rotate axis to the y-forward axis. + LMatrix4d r1; + look_at(r1, v1, rotate_axis, CS_zup_right); + + _matrix = + LMatrix4d::translate_mat(-_center) * + r1 * + LMatrix4d::scale_mat(1.0, _axis_scale, 1.0) * + LMatrix4d::scale_mat(_overall_scale) * + invert(r1) * + LMatrix4d::rotate_mat(_angle, rotate_axis, CS_zup_right) * + LMatrix4d::translate_mat(_center); +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformRotateScale:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_rotate_and_scale, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _reference_point[0] = iterator.get_be_float64(); + _reference_point[1] = iterator.get_be_float64(); + _reference_point[2] = iterator.get_be_float64(); + _to_point[0] = iterator.get_be_float64(); + _to_point[1] = iterator.get_be_float64(); + _to_point[2] = iterator.get_be_float64(); + _overall_scale = iterator.get_be_float32(); + _axis_scale = iterator.get_be_float32(); + _angle = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformRotateScale:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_put); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float64(_reference_point[0]); + datagram.add_be_float64(_reference_point[1]); + datagram.add_be_float64(_reference_point[2]); + datagram.add_be_float64(_to_point[0]); + datagram.add_be_float64(_to_point[1]); + datagram.add_be_float64(_to_point[2]); + datagram.add_be_float32(_overall_scale); + datagram.add_be_float32(_axis_scale); + datagram.add_be_float32(_angle); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltTransformRotateScale.h b/pandatool/src/flt/fltTransformRotateScale.h new file mode 100644 index 00000000..29b72ddd --- /dev/null +++ b/pandatool/src/flt/fltTransformRotateScale.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformRotateScale.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTTRANSFORMROTATESCALE_H +#define FLTTRANSFORMROTATESCALE_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A combination rotation and scale. This is sometimes called "Rotate To + * Point" within MultiGen. + */ +class FltTransformRotateScale : public FltTransformRecord { +public: + FltTransformRotateScale(FltHeader *header); + + void set(const LPoint3d ¢er, const LPoint3d &reference_point, + const LPoint3d &to_point, bool axis_scale); + + const LPoint3d &get_center() const; + const LPoint3d &get_reference_point() const; + const LPoint3d &get_to_point() const; + PN_stdfloat get_overall_scale() const; + PN_stdfloat get_axis_scale() const; + PN_stdfloat get_angle() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LPoint3d _reference_point; + LPoint3d _to_point; + PN_stdfloat _overall_scale; + PN_stdfloat _axis_scale; + PN_stdfloat _angle; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformRotateScale", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformScale.cxx b/pandatool/src/flt/fltTransformScale.cxx new file mode 100644 index 00000000..69ae6f4c --- /dev/null +++ b/pandatool/src/flt/fltTransformScale.cxx @@ -0,0 +1,142 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformScale.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltTransformScale.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformScale::_type_handle; + +/** + * + */ +FltTransformScale:: +FltTransformScale(FltHeader *header) : FltTransformRecord(header) { + _center.set(0.0, 0.0, 0.0); + _scale.set(1.0, 1.0, 1.0); +} + +/** + * Defines the scale. + */ +void FltTransformScale:: +set(const LPoint3d ¢er, const LVecBase3 &scale) { + _center = center; + _scale = scale; + + recompute_matrix(); +} + +/** + * Returns true if the center is specified, false if it is not. For some + * reason, MultiGen stores large negative numbers in for the center if it is + * not specified. It is unclear what the purpose of this is. + */ +bool FltTransformScale:: +has_center() const { + return + _center[0] > -1e+08 && + _center[1] > -1e+08 && + _center[2] > -1e+08; +} + +/** + * + */ +const LPoint3d &FltTransformScale:: +get_center() const { + return _center; +} + +/** + * + */ +const LVecBase3 &FltTransformScale:: +get_scale() const { + return _scale; +} + +/** + * + */ +void FltTransformScale:: +recompute_matrix() { + if (has_center()) { + _matrix = + LMatrix4d::translate_mat(-_center) * + LMatrix4d::scale_mat(LCAST(double, _scale)) * + LMatrix4d::translate_mat(_center); + } else { + _matrix = + LMatrix4d::scale_mat(LCAST(double, _scale)); + } +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformScale:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_scale, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); + + _center[0] = iterator.get_be_float64(); + _center[1] = iterator.get_be_float64(); + _center[2] = iterator.get_be_float64(); + _scale[0] = iterator.get_be_float32(); + _scale[1] = iterator.get_be_float32(); + _scale[2] = iterator.get_be_float32(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformScale:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_scale); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_center[0]); + datagram.add_be_float64(_center[1]); + datagram.add_be_float64(_center[2]); + datagram.add_be_float32(_scale[0]); + datagram.add_be_float32(_scale[1]); + datagram.add_be_float32(_scale[2]); + + datagram.pad_bytes(4); // Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltTransformScale.h b/pandatool/src/flt/fltTransformScale.h new file mode 100644 index 00000000..01bc6296 --- /dev/null +++ b/pandatool/src/flt/fltTransformScale.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformScale.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTTRANSFORMSCALE_H +#define FLTTRANSFORMSCALE_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A transformation that applies a (possibly nonuniform) scale. + */ +class FltTransformScale : public FltTransformRecord { +public: + FltTransformScale(FltHeader *header); + + void set(const LPoint3d ¢er, const LVecBase3 &scale); + + bool has_center() const; + const LPoint3d &get_center() const; + const LVecBase3 &get_scale() const; + +private: + void recompute_matrix(); + + LPoint3d _center; + LVecBase3 _scale; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformScale", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltTransformTranslate.cxx b/pandatool/src/flt/fltTransformTranslate.cxx new file mode 100644 index 00000000..1a06d761 --- /dev/null +++ b/pandatool/src/flt/fltTransformTranslate.cxx @@ -0,0 +1,122 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformTranslate.cxx + * @author drose + * @date 2000-08-30 + */ + +#include "fltTransformTranslate.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltTransformTranslate::_type_handle; + +/** + * + */ +FltTransformTranslate:: +FltTransformTranslate(FltHeader *header) : FltTransformRecord(header) { + _from.set(0.0, 0.0, 0.0); + _delta.set(0.0, 0.0, 0.0); +} + +/** + * Defines the translation. The "from" point seems to be pretty much ignored. + */ +void FltTransformTranslate:: +set(const LPoint3d &from, const LVector3d &delta) { + _from = from; + _delta = delta; + + recompute_matrix(); +} + +/** + * Returns the reference point of the translation. This is largely + * meaningless. + */ +const LPoint3d &FltTransformTranslate:: +get_from() const { + return _from; +} + +/** + * + */ +const LVector3d &FltTransformTranslate:: +get_delta() const { + return _delta; +} + +/** + * + */ +void FltTransformTranslate:: +recompute_matrix() { + _matrix = LMatrix4d::translate_mat(_delta); +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltTransformTranslate:: +extract_record(FltRecordReader &reader) { + if (!FltTransformRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_translate, false); + DatagramIterator &iterator = reader.get_iterator(); + + iterator.skip_bytes(4); // Undocumented additional padding. + + _from[0] = iterator.get_be_float64(); + _from[1] = iterator.get_be_float64(); + _from[2] = iterator.get_be_float64(); + _delta[0] = iterator.get_be_float64(); + _delta[1] = iterator.get_be_float64(); + _delta[2] = iterator.get_be_float64(); + + // iterator.skip_bytes(4); Undocumented additional padding. + + recompute_matrix(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltTransformTranslate:: +build_record(FltRecordWriter &writer) const { + if (!FltTransformRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_translate); + Datagram &datagram = writer.update_datagram(); + + datagram.pad_bytes(4); // Undocumented additional padding. + + datagram.add_be_float64(_from[0]); + datagram.add_be_float64(_from[1]); + datagram.add_be_float64(_from[2]); + datagram.add_be_float64(_delta[0]); + datagram.add_be_float64(_delta[1]); + datagram.add_be_float64(_delta[2]); + + // datagram.pad_bytes(4); Undocumented additional padding. + + return true; +} diff --git a/pandatool/src/flt/fltTransformTranslate.h b/pandatool/src/flt/fltTransformTranslate.h new file mode 100644 index 00000000..c9a0c879 --- /dev/null +++ b/pandatool/src/flt/fltTransformTranslate.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTransformTranslate.h + * @author drose + * @date 2000-08-30 + */ + +#ifndef FLTTRANSFORMTRANSLATE_H +#define FLTTRANSFORMTRANSLATE_H + +#include "pandatoolbase.h" + +#include "fltTransformRecord.h" + +/** + * A transformation that applies a translation. + */ +class FltTransformTranslate : public FltTransformRecord { +public: + FltTransformTranslate(FltHeader *header); + + void set(const LPoint3d &from, const LVector3d &delta); + + const LPoint3d &get_from() const; + const LVector3d &get_delta() const; + +private: + void recompute_matrix(); + + LPoint3d _from; + LVector3d _delta; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltTransformRecord::init_type(); + register_type(_type_handle, "FltTransformTranslate", + FltTransformRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/fltUnsupportedRecord.cxx b/pandatool/src/flt/fltUnsupportedRecord.cxx new file mode 100644 index 00000000..7a6e67aa --- /dev/null +++ b/pandatool/src/flt/fltUnsupportedRecord.cxx @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltUnsupportedRecord.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "fltUnsupportedRecord.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltUnsupportedRecord::_type_handle; + +/** + * + */ +FltUnsupportedRecord:: +FltUnsupportedRecord(FltHeader *header) : FltRecord(header) { + _opcode = FO_none; +} + +/** + * Writes a quick one-line description of the bead, but not its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltUnsupportedRecord:: +output(std::ostream &out) const { + out << "Unsupported(" << _opcode << ")"; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltUnsupportedRecord:: +extract_record(FltRecordReader &reader) { + _opcode = reader.get_opcode(); + _datagram = reader.get_datagram(); + + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltUnsupportedRecord:: +build_record(FltRecordWriter &writer) const { + writer.set_opcode(_opcode); + writer.set_datagram(_datagram); + return true; +} diff --git a/pandatool/src/flt/fltUnsupportedRecord.h b/pandatool/src/flt/fltUnsupportedRecord.h new file mode 100644 index 00000000..a358577d --- /dev/null +++ b/pandatool/src/flt/fltUnsupportedRecord.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltUnsupportedRecord.h + * @author drose + * @date 2000-08-24 + */ + +#ifndef FLTUNSUPPORTEDRECORD_H +#define FLTUNSUPPORTEDRECORD_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "datagram.h" + +/** + * + */ +class FltUnsupportedRecord : public FltRecord { +public: + FltUnsupportedRecord(FltHeader *header); + + virtual void output(std::ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + FltOpcode _opcode; + Datagram _datagram; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltUnsupportedRecord", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#endif diff --git a/pandatool/src/flt/fltVectorRecord.cxx b/pandatool/src/flt/fltVectorRecord.cxx new file mode 100644 index 00000000..dbe23a63 --- /dev/null +++ b/pandatool/src/flt/fltVectorRecord.cxx @@ -0,0 +1,77 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVectorRecord.cxx + * @author drose + * @date 2002-08-30 + */ + +#include "fltVectorRecord.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" + +TypeHandle FltVectorRecord::_type_handle; + +/** + * + */ +FltVectorRecord:: +FltVectorRecord(FltHeader *header) : FltRecord(header) { + _vector.set(0.0f, 0.0f, 0.0f); +} + +/** + * Returns the vector value. + */ +const LVector3 &FltVectorRecord:: +get_vector() const { + return _vector; +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltVectorRecord:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_vector, false); + DatagramIterator &iterator = reader.get_iterator(); + + _vector[0] = iterator.get_be_float32(); + _vector[1] = iterator.get_be_float32(); + _vector[2] = iterator.get_be_float32(); + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltVectorRecord:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_vector); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_float32(_vector[0]); + datagram.add_be_float32(_vector[1]); + datagram.add_be_float32(_vector[2]); + + return true; +} diff --git a/pandatool/src/flt/fltVectorRecord.h b/pandatool/src/flt/fltVectorRecord.h new file mode 100644 index 00000000..e099652e --- /dev/null +++ b/pandatool/src/flt/fltVectorRecord.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVectorRecord.h + * @author drose + * @date 2002-08-30 + */ + +#ifndef FLTVECTORRECORD_H +#define FLTVECTORRECORD_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" + +#include "luse.h" + +/** + * This is an ancillary record of the old (pre-15.4) face node. Its only use + * is to provide the direction vector for unidirectional and bidirectional + * light point faces. + */ +class FltVectorRecord : public FltRecord { +public: + FltVectorRecord(FltHeader *header); + + const LVector3 &get_vector() const; + +protected: + LVector3 _vector; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltVectorRecord", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltBead; +}; + +#endif diff --git a/pandatool/src/flt/fltVertex.I b/pandatool/src/flt/fltVertex.I new file mode 100644 index 00000000..2f7717af --- /dev/null +++ b/pandatool/src/flt/fltVertex.I @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVertex.I + * @author drose + * @date 2000-08-30 + */ + +/** + * Returns true if the vertex has a primary color indicated, false otherwise. + */ +INLINE bool FltVertex:: +has_color() const { + // Even if the no_color bit is not set, if the color_index is -1, the vertex + // doesn't have a color (unless we've got packed color). + return ((_flags & F_no_color) == 0 && + (_color_index != -1 || ((_flags & F_packed_color) != 0))); +} + +/** + * Sets the color of the vertex, using the packed color convention. The alpha + * component is ignored. + */ +INLINE void FltVertex:: +set_color(const LColor &color) { + set_rgb(LRGBColor(color[0], color[1], color[2])); +} diff --git a/pandatool/src/flt/fltVertex.cxx b/pandatool/src/flt/fltVertex.cxx new file mode 100644 index 00000000..77cac997 --- /dev/null +++ b/pandatool/src/flt/fltVertex.cxx @@ -0,0 +1,259 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVertex.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltVertex.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltVertex::_type_handle; + +/** + * + */ +FltVertex:: +FltVertex(FltHeader *header) : FltRecord(header) { + _color_name_index = 0; + _flags = F_no_color; + _pos.set(0.0, 0.0, 0.0); + _normal.set(0.0, 0.0, 0.0); + _uv.set(0.0, 0.0); + _color_index = 0; + + _has_normal = false; + _has_uv = false; +} + +/** + * Returns the opcode that this record will be written as. + */ +FltOpcode FltVertex:: +get_opcode() const { + if (_has_normal) { + if (_has_uv) { + return FO_vertex_cnu; + } else { + return FO_vertex_cn; + } + } else { + if (_has_uv) { + return FO_vertex_cu; + } else { + return FO_vertex_c; + } + } +} + +/** + * Returns the length of this record in bytes as it will be written to the flt + * file. + */ +int FltVertex:: +get_record_length() const { + if (_header->get_flt_version() < 1520) { + // Version 14.2 + switch (get_opcode()) { + case FO_vertex_c: + return 36; + + case FO_vertex_cn: + return 48; + + case FO_vertex_cnu: + return 56; + + case FO_vertex_cu: + return 44; + + default: + nassertr(false, 0); + } + + } else { + // Version 15.2 and higher + switch (get_opcode()) { + case FO_vertex_c: + return 40; + + case FO_vertex_cn: + return 56; + + case FO_vertex_cnu: + return 64; + + case FO_vertex_cu: + return 48; + + default: + nassertr(false, 0); + } + } + + return 0; +} + +/** + * If has_color() indicates true, returns the color of the vertex, as a four- + * component value. In the case of a vertex, the alpha channel will always be + * 1.0, as MultiGen does not store transparency per-vertex. + */ +LColor FltVertex:: +get_color() const { + nassertr(has_color(), LColor(0.0, 0.0, 0.0, 0.0)); + + return _header->get_color(_color_index, (_flags & F_packed_color) != 0, + _packed_color, 0); +} + +/** + * If has_color() indicates true, returns the color of the vertex, as a three- + * component value. + */ +LRGBColor FltVertex:: +get_rgb() const { + nassertr(has_color(), LRGBColor(0.0, 0.0, 0.0)); + + return _header->get_rgb(_color_index, (_flags & F_packed_color) != 0, + _packed_color); +} + +/** + * Sets the color of the vertex, using the packed color convention. + */ +void FltVertex:: +set_rgb(const LRGBColor &rgb) { + _packed_color.set_rgb(rgb); + _flags = ((_flags & ~F_no_color) | F_packed_color); +} + +/** + * Fills in the information in this record based on the information given in + * the indicated datagram, whose opcode has already been read. Returns true + * on success, false if the datagram is invalid. + */ +bool FltVertex:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + switch (reader.get_opcode()) { + case FO_vertex_c: + _has_normal = false; + _has_uv = false; + break; + + case FO_vertex_cn: + _has_normal = true; + _has_uv = false; + break; + + case FO_vertex_cnu: + _has_normal = true; + _has_uv = true; + break; + + case FO_vertex_cu: + _has_normal = false; + _has_uv = true; + break; + + default: + nassertr(false, false); + } + + DatagramIterator &iterator = reader.get_iterator(); + + _color_name_index = iterator.get_be_int16(); + _flags = iterator.get_be_uint16(); + _pos[0] = iterator.get_be_float64(); + _pos[1] = iterator.get_be_float64(); + _pos[2] = iterator.get_be_float64(); + + if (_has_normal) { + _normal[0] = iterator.get_be_float32(); + _normal[1] = iterator.get_be_float32(); + _normal[2] = iterator.get_be_float32(); + } + if (_has_uv) { + _uv[0] = iterator.get_be_float32(); + _uv[1] = iterator.get_be_float32(); + } + + if (iterator.get_remaining_size() > 0) { + if (!_packed_color.extract_record(reader)) { + return false; + } + if (_header->get_flt_version() >= 1520) { + _color_index = iterator.get_be_int32(); + + if (_has_normal && iterator.get_remaining_size() > 0) { + // If we extracted a normal, our double-word alignment is off; now we + // have a few extra bytes to ignore. + iterator.skip_bytes(4); + } + } + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltVertex:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(get_opcode()); + Datagram &datagram = writer.update_datagram(); + + datagram.add_be_int16(_color_name_index); + datagram.add_be_uint16(_flags); + datagram.add_be_float64(_pos[0]); + datagram.add_be_float64(_pos[1]); + datagram.add_be_float64(_pos[2]); + + if (_has_normal) { + datagram.add_be_float32(_normal[0]); + datagram.add_be_float32(_normal[1]); + datagram.add_be_float32(_normal[2]); + } + if (_has_uv) { + datagram.add_be_float32(_uv[0]); + datagram.add_be_float32(_uv[1]); + } + + if (!_packed_color.build_record(writer)) { + return false; + } + + if (_header->get_flt_version() >= 1520) { + // New with 15.2 + datagram.add_be_uint32(_color_index); + + if (_has_normal) { + // If we added a normal, our double-word alignment is off; now we have a + // few extra bytes to add. + datagram.pad_bytes(4); + } + } + + nassertr((int)datagram.get_length() == get_record_length() - 4, true); + return true; +} diff --git a/pandatool/src/flt/fltVertex.h b/pandatool/src/flt/fltVertex.h new file mode 100644 index 00000000..9c02c372 --- /dev/null +++ b/pandatool/src/flt/fltVertex.h @@ -0,0 +1,91 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVertex.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTVERTEX_H +#define FLTVERTEX_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" +#include "fltPackedColor.h" + +#include "luse.h" + +/** + * Represents a single vertex in the vertex palette. Flt files index vertices + * by their byte offset in the vertex palette; within this library, we map + * those byte offsets to pointers automatically. + * + * This may represent a vertex with or without a normal or texture + * coordinates. + */ +class FltVertex : public FltRecord { +public: + FltVertex(FltHeader *header); + + FltOpcode get_opcode() const; + int get_record_length() const; + + enum Flags { + F_hard_edge = 0x8000, + F_normal_frozen = 0x4000, + F_no_color = 0x2000, + F_packed_color = 0x1000 + }; + + int _color_name_index; + unsigned int _flags; + LPoint3d _pos; + LPoint3 _normal; + LPoint2 _uv; + FltPackedColor _packed_color; + int _color_index; + + bool _has_normal; + bool _has_uv; + +public: + INLINE bool has_color() const; + LColor get_color() const; + INLINE void set_color(const LColor &color); + LRGBColor get_rgb() const; + void set_rgb(const LRGBColor &rgb); + + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltVertex", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class FltHeader; +}; + +#include "fltVertex.I" + +#endif diff --git a/pandatool/src/flt/fltVertexList.cxx b/pandatool/src/flt/fltVertexList.cxx new file mode 100644 index 00000000..65da9b59 --- /dev/null +++ b/pandatool/src/flt/fltVertexList.cxx @@ -0,0 +1,116 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVertexList.cxx + * @author drose + * @date 2000-08-25 + */ + +#include "fltVertexList.h" +#include "fltRecordReader.h" +#include "fltRecordWriter.h" +#include "fltHeader.h" + +TypeHandle FltVertexList::_type_handle; + +/** + * + */ +FltVertexList:: +FltVertexList(FltHeader *header) : FltRecord(header) { +} + +/** + * Returns the number of vertices in this vertex list. + */ +int FltVertexList:: +get_num_vertices() const { + return _vertices.size(); +} + +/** + * Returns the nth vertex of this vertex list. + */ +FltVertex *FltVertexList:: +get_vertex(int n) const { + nassertr(n >= 0 && n < (int)_vertices.size(), nullptr); + return _vertices[n]; +} + +/** + * Removes all vertices from this vertex list. + */ +void FltVertexList:: +clear_vertices() { + _vertices.clear(); +} + +/** + * Adds a new vertex to the end of the vertex list. + */ +void FltVertexList:: +add_vertex(FltVertex *vertex) { + _header->add_vertex(vertex); + _vertices.push_back(vertex); +} + +/** + * Writes a quick one-line description of the record, but not its children. + * This is a human-readable description, primarily for debugging; to write a + * flt file, use FltHeader::write_flt(). + */ +void FltVertexList:: +output(std::ostream &out) const { + out << _vertices.size() << " vertices"; +} + +/** + * Fills in the information in this bead based on the information given in the + * indicated datagram, whose opcode has already been read. Returns true on + * success, false if the datagram is invalid. + */ +bool FltVertexList:: +extract_record(FltRecordReader &reader) { + if (!FltRecord::extract_record(reader)) { + return false; + } + + nassertr(reader.get_opcode() == FO_vertex_list, false); + DatagramIterator &iterator = reader.get_iterator(); + + _vertices.clear(); + while (iterator.get_remaining_size() >= 4) { + int vertex_offset = iterator.get_be_int32(); + _vertices.push_back(_header->get_vertex_by_offset(vertex_offset)); + } + + check_remaining_size(iterator); + return true; +} + +/** + * Fills up the current record on the FltRecordWriter with data for this + * record, but does not advance the writer. Returns true on success, false if + * there is some error. + */ +bool FltVertexList:: +build_record(FltRecordWriter &writer) const { + if (!FltRecord::build_record(writer)) { + return false; + } + + writer.set_opcode(FO_vertex_list); + Datagram &datagram = writer.update_datagram(); + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + datagram.add_be_uint32(_header->get_offset_by_vertex(*vi)); + } + + return true; +} diff --git a/pandatool/src/flt/fltVertexList.h b/pandatool/src/flt/fltVertexList.h new file mode 100644 index 00000000..5fd53ad8 --- /dev/null +++ b/pandatool/src/flt/fltVertexList.h @@ -0,0 +1,65 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltVertexList.h + * @author drose + * @date 2000-08-25 + */ + +#ifndef FLTVERTEXLIST_H +#define FLTVERTEXLIST_H + +#include "pandatoolbase.h" + +#include "fltRecord.h" +#include "fltPackedColor.h" +#include "fltVertex.h" + +#include "pointerTo.h" + +/** + * A list of vertices, typically added as a child of a face bead. + */ +class FltVertexList : public FltRecord { +public: + FltVertexList(FltHeader *header); + + int get_num_vertices() const; + FltVertex *get_vertex(int n) const; + void clear_vertices(); + void add_vertex(FltVertex *vertex); + + virtual void output(std::ostream &out) const; + +protected: + virtual bool extract_record(FltRecordReader &reader); + virtual bool build_record(FltRecordWriter &writer) const; + +private: + typedef pvector Vertices; + Vertices _vertices; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + FltRecord::init_type(); + register_type(_type_handle, "FltVertexList", + FltRecord::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/flt/p3flt_composite1.cxx b/pandatool/src/flt/p3flt_composite1.cxx new file mode 100644 index 00000000..8c9f9fd2 --- /dev/null +++ b/pandatool/src/flt/p3flt_composite1.cxx @@ -0,0 +1,41 @@ + +#include "config_flt.cxx" +#include "fltBead.cxx" +#include "fltBeadID.cxx" +#include "fltCurve.cxx" +#include "fltError.cxx" +#include "fltExternalReference.cxx" +#include "fltEyepoint.cxx" +#include "fltFace.cxx" +#include "fltGeometry.cxx" +#include "fltGroup.cxx" +#include "fltHeader.cxx" +#include "fltInstanceDefinition.cxx" +#include "fltInstanceRef.cxx" +#include "fltLOD.cxx" +#include "fltLightSourceDefinition.cxx" +#include "fltLocalVertexPool.cxx" +#include "fltMaterial.cxx" +#include "fltMesh.cxx" +#include "fltMeshPrimitive.cxx" +#include "fltObject.cxx" +#include "fltOpcode.cxx" +#include "fltPackedColor.cxx" +#include "fltRecord.cxx" +#include "fltRecordReader.cxx" +#include "fltRecordWriter.cxx" +#include "fltTexture.cxx" +#include "fltTrackplane.cxx" +#include "fltTransformGeneralMatrix.cxx" +#include "fltTransformPut.cxx" +#include "fltTransformRecord.cxx" +#include "fltTransformRotateAboutEdge.cxx" +#include "fltTransformRotateAboutPoint.cxx" +#include "fltTransformRotateScale.cxx" +#include "fltTransformScale.cxx" +#include "fltTransformTranslate.cxx" +#include "fltUnsupportedRecord.cxx" +#include "fltVectorRecord.cxx" +#include "fltVertex.cxx" +#include "fltVertexList.cxx" + diff --git a/pandatool/src/fltegg/CMakeLists.txt b/pandatool/src/fltegg/CMakeLists.txt new file mode 100644 index 00000000..5a61e47a --- /dev/null +++ b/pandatool/src/fltegg/CMakeLists.txt @@ -0,0 +1,19 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3FLTEGG_HEADERS + fltToEggConverter.h fltToEggConverter.I + fltToEggLevelState.h fltToEggLevelState.I +) + +set(P3FLTEGG_SOURCES + fltToEggConverter.cxx + fltToEggLevelState.cxx +) + +add_library(p3fltegg STATIC ${P3FLTEGG_HEADERS} ${P3FLTEGG_SOURCES}) +target_link_libraries(p3fltegg p3flt p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/fltegg/fltToEggConverter.I b/pandatool/src/fltegg/fltToEggConverter.I new file mode 100644 index 00000000..1168eb64 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggConverter.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggConverter.I + * @author drose + * @date 2001-04-17 + */ diff --git a/pandatool/src/fltegg/fltToEggConverter.cxx b/pandatool/src/fltegg/fltToEggConverter.cxx new file mode 100644 index 00000000..98b01938 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggConverter.cxx @@ -0,0 +1,839 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggConverter.cxx + * @author drose + * @date 2001-04-17 + */ + +#include "fltToEggConverter.h" + +#include "fltRecord.h" +#include "fltLOD.h" +#include "fltGroup.h" +#include "fltObject.h" +#include "fltBeadID.h" +#include "fltBead.h" +#include "fltFace.h" +#include "fltVertex.h" +#include "fltVertexList.h" +#include "fltExternalReference.h" +#include "dcast.h" +#include "eggData.h" +#include "eggGroup.h" +#include "eggSwitchCondition.h" +#include "eggPrimitive.h" +#include "eggPolygon.h" +#include "eggPoint.h" +#include "eggVertex.h" +#include "eggVertexPool.h" +#include "eggExternalReference.h" +#include "string_utils.h" + +using std::string; + + +/** + * + */ +FltToEggConverter:: +FltToEggConverter() { + _compose_transforms = false; + _flt_units = DU_invalid; +} + +/** + * + */ +FltToEggConverter:: +FltToEggConverter(const FltToEggConverter ©) : + SomethingToEggConverter(copy), + _compose_transforms(copy._compose_transforms) +{ +} + +/** + * + */ +FltToEggConverter:: +~FltToEggConverter() { + cleanup(); +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *FltToEggConverter:: +make_copy() { + return new FltToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +string FltToEggConverter:: +get_name() const { + return "MultiGen"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +string FltToEggConverter:: +get_extension() const { + return "flt"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool FltToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + * + * This is designed to be as generic as possible, generally in support of run- + * time loading. Command-line converters may choose to use convert_flt() + * instead, as it provides more control. + */ +bool FltToEggConverter:: +convert_file(const Filename &filename) { + PT(FltHeader) header = new FltHeader(_path_replace); + + nout << "Reading " << filename << "\n"; + FltError result = header->read_flt(filename); + if (result != FE_ok) { + nout << "Unable to read: " << result << "\n"; + return false; + } + + header->check_version(); + + _flt_units = header->get_units(); + + return convert_flt(header); +} + +/** + * This may be called after convert_file() has been called and returned true, + * indicating a successful conversion. It will return the distance units + * represented by the converted egg file, if known, or DU_invalid if not + * known. + */ +DistanceUnit FltToEggConverter:: +get_input_units() { + return _flt_units; +} + +/** + * Fills up the egg_data structure according to the indicated lwo structure. + */ +bool FltToEggConverter:: +convert_flt(const FltHeader *flt_header) { + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_zup_right); + } + + clear_error(); + _flt_header = flt_header; + + // Generate a default vertex pool. + _main_egg_vpool = new EggVertexPool("vpool"); + _egg_data->add_child(_main_egg_vpool.p()); + + // We could populate the vertex pool right away, but it's better to defer + // each vertex until we encounter it, since some of the vertices may need to + // be adjusted to match the particular polygon they're assigned to (for + // instance, to apply a transparency or something). + + FltToEggLevelState state(this); + state._egg_parent = _egg_data; + convert_record(_flt_header, state); + + if (_main_egg_vpool->empty()) { + // If we didn't get any global vertices, remove the vertex pool just for + // cleanliness. + _egg_data->remove_child(_main_egg_vpool.p()); + } + + cleanup(); + + return !had_error(); +} + +/** + * Frees all the internal data structures after we're done converting, and + * resets the converter to its initial state. + */ +void FltToEggConverter:: +cleanup() { + _flt_header.clear(); + _main_egg_vpool.clear(); + _textures.clear(); +} + +/** + * Converts the record and all of its children. + */ +void FltToEggConverter:: +convert_record(const FltRecord *flt_record, FltToEggLevelState &state) { + int num_children = flt_record->get_num_children(); + + for (int i = 0; i < num_children; i++) { + const FltRecord *child = flt_record->get_child(i); + dispatch_record(child, state); + } +} + +/** + * Determines what kind of record this is and calls the appropriate convert + * function. + */ +void FltToEggConverter:: +dispatch_record(const FltRecord *flt_record, FltToEggLevelState &state) { + if (flt_record->is_of_type(FltLOD::get_class_type())) { + convert_lod(DCAST(FltLOD, flt_record), state); + + } else if (flt_record->is_of_type(FltGroup::get_class_type())) { + convert_group(DCAST(FltGroup, flt_record), state); + + } else if (flt_record->is_of_type(FltObject::get_class_type())) { + convert_object(DCAST(FltObject, flt_record), state); + + } else if (flt_record->is_of_type(FltFace::get_class_type())) { + convert_face(DCAST(FltFace, flt_record), state); + + } else if (flt_record->is_of_type(FltExternalReference::get_class_type())) { + convert_ext_ref(DCAST(FltExternalReference, flt_record), state); + + // Fallbacks. + } else if (flt_record->is_of_type(FltBeadID::get_class_type())) { + convert_bead_id(DCAST(FltBeadID, flt_record), state); + + } else if (flt_record->is_of_type(FltBead::get_class_type())) { + convert_bead(DCAST(FltBead, flt_record), state); + + } else { + convert_record(flt_record, state); + } +} + +/** + * Converts the LOD bead and all of its children. + */ +void FltToEggConverter:: +convert_lod(const FltLOD *flt_lod, FltToEggLevelState &state) { + EggGroup *egg_group = new EggGroup(flt_lod->get_id()); + state._egg_parent->add_child(egg_group); + + EggSwitchConditionDistance lod + (flt_lod->_switch_in, flt_lod->_switch_out, + LPoint3d(flt_lod->_center_x, flt_lod->_center_y, flt_lod->_center_z), + flt_lod->_transition_range); + egg_group->set_lod(lod); + + state.set_transform(flt_lod, egg_group); + parse_comment(flt_lod, egg_group); + + FltToEggLevelState next_state(state); + next_state._egg_parent = egg_group; + convert_record(flt_lod, next_state); +} + +/** + * Converts the group and all of its children. + */ +void FltToEggConverter:: +convert_group(const FltGroup *flt_group, FltToEggLevelState &state) { + EggGroup *egg_group = new EggGroup(flt_group->get_id()); + state._egg_parent->add_child(egg_group); + + if ((flt_group->_flags & FltGroup::F_forward_animation) != 0) { + // It's a sequence animation. + egg_group->set_switch_flag(true); + egg_group->set_switch_fps(24.0); + } + + state.set_transform(flt_group, egg_group); + parse_comment(flt_group, egg_group); + + // *** replicate count. + + FltToEggLevelState next_state(state); + next_state._egg_parent = egg_group; + convert_record(flt_group, next_state); +} + +/** + * Converts the object and all of its children. + */ +void FltToEggConverter:: +convert_object(const FltObject *flt_object, FltToEggLevelState &state) { + EggGroup *egg_group = new EggGroup(flt_object->get_id()); + state._egg_parent->add_child(egg_group); + + state.set_transform(flt_object, egg_group); + parse_comment(flt_object, egg_group); + + FltToEggLevelState next_state(state); + next_state._flt_object = flt_object; + next_state._egg_parent = egg_group; + convert_record(flt_object, next_state); +} + +/** + * Converts the generic bead (with ID) and all of its children. + */ +void FltToEggConverter:: +convert_bead_id(const FltBeadID *flt_bead, FltToEggLevelState &state) { + nout << "Don't know how to convert beads of type " << flt_bead->get_type() + << "\n"; + EggGroup *egg_group = new EggGroup(flt_bead->get_id()); + state._egg_parent->add_child(egg_group); + + state.set_transform(flt_bead, egg_group); + parse_comment(flt_bead, egg_group); + + FltToEggLevelState next_state(state); + next_state._egg_parent = egg_group; + convert_record(flt_bead, next_state); +} + +/** + * Converts the generic bead (without ID) and all of its children. + */ +void FltToEggConverter:: +convert_bead(const FltBead *flt_bead, FltToEggLevelState &state) { + nout << "Don't know how to convert beads of type " << flt_bead->get_type() + << "\n"; + EggGroup *egg_group = new EggGroup; + state._egg_parent->add_child(egg_group); + + state.set_transform(flt_bead, egg_group); + parse_comment(flt_bead, egg_group); + + FltToEggLevelState next_state(state); + next_state._egg_parent = egg_group; + convert_record(flt_bead, next_state); +} + +/** + * Converts the face and all of its children. + */ +void FltToEggConverter:: +convert_face(const FltFace *flt_face, FltToEggLevelState &state) { + bool is_light; + switch (flt_face->_draw_type) { + case FltGeometry::DT_omni_light: + case FltGeometry::DT_uni_light: + case FltGeometry::DT_bi_light: + is_light = true; + break; + + default: + is_light = false; + } + + PT(EggPrimitive) egg_prim; + if (is_light) { + egg_prim = new EggPoint; + } else { + egg_prim = new EggPolygon; + } + + // Collect the vertices for this primitive. + pvector< PT_EggVertex > vertices; + + const FltVertexList *vlist = nullptr; + int num_children = flt_face->get_num_children(); + for (int i = 0; i < num_children && vlist == nullptr; i++) { + const FltRecord *child = flt_face->get_child(i); + if (child->is_of_type(FltVertexList::get_class_type())) { + vlist = DCAST(FltVertexList, child); + } + } + + if (vlist != nullptr) { + int num_vertices = vlist->get_num_vertices(); + for (int i = 0; i < num_vertices; i++) { + FltVertex *flt_vertex = vlist->get_vertex(i); + vertices.push_back(make_egg_vertex(flt_vertex)); + } + } + + setup_geometry(flt_face, state, egg_prim, _main_egg_vpool, vertices); +} + +/** + * Converts the external reference node. + */ +void FltToEggConverter:: +convert_ext_ref(const FltExternalReference *flt_ext, FltToEggLevelState &state) { + // Get a group node to put the reference into. + EggGroupNode *egg_parent = + state.get_synthetic_group("", flt_ext); + + handle_external_reference(egg_parent, flt_ext->get_ref_filename()); +} + +/** + * Applies the state indicated in the FltGeometry record to the indicated + * EggPrimitive and all of its indicated vertices, and then officially adds + * the vertices to the vertex pool and to the primitive, and adds the + * primitive to its appropriate parent. + */ +void FltToEggConverter:: +setup_geometry(const FltGeometry *flt_geom, FltToEggLevelState &state, + EggPrimitive *egg_prim, EggVertexPool *egg_vpool, + const FltToEggConverter::EggVertices &vertices) { + + // Determine what the appropriate parent will be. + EggGroupNode *egg_parent = + state.get_synthetic_group(flt_geom->get_id(), flt_geom, + flt_geom->_billboard_type); + + // Create a new state to reflect the new parent. + FltToEggLevelState next_state(state); + next_state._egg_parent = egg_parent; + + // Check for decals onto the primitive. + convert_subfaces(flt_geom, next_state); + + // Add the primitive to its new home. + next_state._egg_parent->add_child(egg_prim); + + // Now examine the vertices. + EggVertices::const_iterator vi; + + bool use_vertex_color = true; + bool keep_normals = true; + switch (flt_geom->_light_mode) { + case FltGeometry::LM_face_no_normal: + use_vertex_color = false; + keep_normals = false; + break; + + case FltGeometry::LM_vertex_no_normal: + use_vertex_color = true; + keep_normals = false; + break; + + case FltGeometry::LM_face_with_normal: + use_vertex_color = false; + keep_normals = true; + break; + + case FltGeometry::LM_vertex_with_normal: + use_vertex_color = true; + keep_normals = true; + break; + } + + LColor face_color = flt_geom->get_color(); + + if (state._flt_object != nullptr) { + // If we have a FltObject above us, it might also specify a transparency. + // This combines with our existing transparency. + PN_stdfloat alpha = 1.0 - (state._flt_object->_transparency / 65535.0); + face_color[3] *= alpha; + } + + egg_prim->set_color(face_color); + + if (flt_geom->has_texture()) { + // If the geometry has a texture, apply it. + egg_prim->set_texture(make_egg_texture(flt_geom->get_texture())); + + if (flt_geom->_texwhite) { + // If the geometry should be colored white under the texture, then + // eliminate vertex colors. + use_vertex_color = false; + } + } + + if (use_vertex_color) { + // If we're to use vertex color instead of the face color, remove the face + // color to eliminate any ambiguity. + egg_prim->clear_color(); + + // Also, make sure the transparency is set correctly across all vertices. + for (vi = vertices.begin(); vi != vertices.end(); ++vi) { + EggVertex *vertex = (*vi); + if (vertex->has_color()) { + LColor vertex_color = vertex->get_color(); + vertex_color[3] = face_color[3]; + vertex->set_color(vertex_color); + } else { + if (flt_geom->has_color()) { + // If a vertex doesn't have a color but the face does, set the + // vertex to use the face color. + vertex->set_color(face_color); + } + } + } + + } else { + // If we're to use face color instead of vertex color, remove the vertex + // color to eliminate any ambiguity. + for (vi = vertices.begin(); vi != vertices.end(); ++vi) { + (*vi)->clear_color(); + } + } + + if (!keep_normals) { + // If we're not to use the normals, then eliminate them. + for (vi = vertices.begin(); vi != vertices.end(); ++vi) { + (*vi)->clear_normal(); + } + } + + if (flt_geom->_draw_type == FltGeometry::DT_solid_no_cull) { + // A double-sided polygon. + egg_prim->set_bface_flag(true); + } + + for (vi = vertices.begin(); vi != vertices.end(); ++vi) { + EggVertex *egg_vertex = egg_vpool->create_unique_vertex(*(*vi)); + egg_prim->add_vertex(egg_vertex); + } + + parse_comment(flt_geom, egg_prim); +} + +/** + * Records all of the subfaces of the indicated group as coplanar polygons + * (i.e. decals) of the group. + * + * If coplanar polygons exist, the state is modified so that _egg_parent is + * the new group to which the base polygons should be added. Therefore, + * subfaces should be defined before the ordinary children are processed. + */ +void FltToEggConverter:: +convert_subfaces(const FltRecord *flt_record, FltToEggLevelState &state) { + int num_subfaces = flt_record->get_num_subfaces(); + if (num_subfaces == 0) { + // No subfaces. + return; + } + + // Create a new group to contain the base polygons. + EggGroup *egg_group = new EggGroup("decal_base"); + state._egg_parent->add_child(egg_group); + state._egg_parent = egg_group; + + egg_group->set_decal_flag(true); + + // Now create a nested group to hold the decals. + EggGroup *decal_group = new EggGroup("decals"); + egg_group->add_child(decal_group); + egg_group = decal_group; + + FltToEggLevelState next_state(state); + next_state._egg_parent = decal_group; + + for (int i = 0; i < num_subfaces; i++) { + const FltRecord *subface = flt_record->get_subface(i); + dispatch_record(subface, next_state); + } +} + +/** + * Scans the comment on this record for " { ... }" and parses the + * enclosed string as if it appeared in the egg file. Returns true on + * success, false on syntax error (in which case _error is also set to true). + */ +bool FltToEggConverter:: +parse_comment(const FltBeadID *flt_bead, EggNode *egg_node) { + return parse_comment(flt_bead->get_comment(), flt_bead->get_id(), egg_node); +} + +/** + * Scans the comment on this record for " { ... }" and parses the + * enclosed string as if it appeared in the egg file. Returns true on + * success, false on syntax error (in which case _error is also set to true). + */ +bool FltToEggConverter:: +parse_comment(const FltBead *flt_bead, EggNode *egg_node) { + return parse_comment(flt_bead->get_comment(), "anonymous", egg_node); +} + +/** + * Scans the comment on this record for " { ... }" and parses the + * enclosed string as if it appeared in the egg file. Returns true on + * success, false on syntax error (in which case _error is also set to true). + */ +bool FltToEggConverter:: +parse_comment(const FltTexture *flt_texture, EggNode *egg_node) { + return parse_comment(flt_texture->get_comment(), + flt_texture->get_texture_filename(), egg_node); +} + +/** + * Scans the comment on this record for " { ... }" and parses the + * enclosed string as if it appeared in the egg file. Returns true on + * success, false on syntax error (in which case _error is also set to true). + */ +bool FltToEggConverter:: +parse_comment(const string &comment, const string &name, + EggNode *egg_node) { + if (comment.empty()) { + // No comment. + return true; + } + + // Scan for . + static const string egg_str = ""; + + size_t p; + p = 0; + while (p < comment.length() && + cmp_nocase(comment.substr(p, 5), egg_str) != 0) { + p++; + } + + if (p >= comment.length()) { + // No "" in the comment. + return true; + } + + p += 5; + // Now scan past whitespace for the open curly brace. + while (p < comment.length() && isspace(comment[p])) { + ++p; + } + if (p >= comment.length() || comment[p] != '{') { + nout << "No opening brace in comment for " + << name << "\n\n"; + _error = true; + return false; + } + + // Here's the beginning of the string after " {". Now lop off the + // closing brace at the end. + ++p; + size_t q = comment.length() - 1; + while (q > p && comment[q] != '}') { + --q; + } + if (q == p) { + nout << "No closing brace in comment for " + << name << "\n\n"; + _error = true; + return false; + } + + string egg_syntax = comment.substr(p, q - p); + + if (!egg_node->parse_egg(egg_syntax)) { + nout << "Syntax error in comment for " + << name << "\n\n"; + _error = true; + return false; + } + + // Correctly parsed! + return true; +} + +/** + * Makes a new EggVertex for the indicated FltVertex. The vertex is not + * automatically added to the vertex pool. + */ +PT_EggVertex FltToEggConverter:: +make_egg_vertex(const FltVertex *flt_vertex) { + PT_EggVertex egg_vertex = new EggVertex; + egg_vertex->set_pos(flt_vertex->_pos); + + if (flt_vertex->_has_normal) { + egg_vertex->set_normal(LCAST(double, flt_vertex->_normal)); + } + + if (flt_vertex->_has_uv) { + egg_vertex->set_uv(LCAST(double, flt_vertex->_uv)); + } + + if (flt_vertex->has_color()) { + egg_vertex->set_color(flt_vertex->get_color()); + } + + return egg_vertex; +} + +/** + * Makes a new EggTexture for the indicated FltTexture, or returns a pointer + * to one previously made for the same FltTexture. + */ +PT_EggTexture FltToEggConverter:: +make_egg_texture(const FltTexture *flt_texture) { + Textures::const_iterator ti; + ti = _textures.find(flt_texture); + if (ti != _textures.end()) { + // There's one previously created. + return (*ti).second; + } + + // Create a new one. + string tref_name = format_string(flt_texture->_pattern_index); + Filename filename = flt_texture->get_texture_filename(); + + PT_EggTexture egg_texture = new EggTexture(tref_name, filename); + + _textures.insert(Textures::value_type(flt_texture, egg_texture)); + + // Set up the texture properties. + + switch (flt_texture->_min_filter) { + case FltTexture::MN_point: + egg_texture->set_minfilter(EggTexture::FT_nearest); + break; + + case FltTexture::MN_bilinear: + egg_texture->set_minfilter(EggTexture::FT_linear); + break; + + case FltTexture::MN_mipmap_point: + egg_texture->set_minfilter(EggTexture::FT_nearest_mipmap_nearest); + break; + + case FltTexture::MN_mipmap_linear: + egg_texture->set_minfilter(EggTexture::FT_nearest_mipmap_linear); + break; + + case FltTexture::MN_mipmap_bilinear: + egg_texture->set_minfilter(EggTexture::FT_linear_mipmap_nearest); + break; + + case FltTexture::MN_mipmap_trilinear: + case FltTexture::MN_OB_mipmap: + egg_texture->set_minfilter(EggTexture::FT_linear_mipmap_linear); + break; + + case FltTexture::MN_bicubic: + case FltTexture::MN_bilinear_gequal: + case FltTexture::MN_bilinear_lequal: + case FltTexture::MN_bicubic_gequal: + case FltTexture::MN_bicubic_lequal: + // Not supported. + break; + } + + switch (flt_texture->_mag_filter) { + case FltTexture::MG_point: + egg_texture->set_magfilter(EggTexture::FT_nearest); + break; + + case FltTexture::MG_bilinear: + egg_texture->set_magfilter(EggTexture::FT_linear); + break; + + case FltTexture::MG_bicubic: + case FltTexture::MG_sharpen: + case FltTexture::MG_add_detail: + case FltTexture::MG_modulate_detail: + case FltTexture::MG_bilinear_gequal: + case FltTexture::MG_bilinear_lequal: + case FltTexture::MG_bicubic_gequal: + case FltTexture::MG_bicubic_lequal: + // Not supported. + break; + } + + switch (flt_texture->_repeat) { + case FltTexture::RT_repeat: + egg_texture->set_wrap_mode(EggTexture::WM_repeat); + break; + + case FltTexture::RT_clamp: + egg_texture->set_wrap_mode(EggTexture::WM_clamp); + break; + } + + switch (flt_texture->_repeat_u) { + case FltTexture::RT_repeat: + egg_texture->set_wrap_u(EggTexture::WM_repeat); + break; + + case FltTexture::RT_clamp: + egg_texture->set_wrap_u(EggTexture::WM_clamp); + break; + } + + switch (flt_texture->_repeat_v) { + case FltTexture::RT_repeat: + egg_texture->set_wrap_v(EggTexture::WM_repeat); + break; + + case FltTexture::RT_clamp: + egg_texture->set_wrap_v(EggTexture::WM_clamp); + break; + } + + switch (flt_texture->_env_type) { + case FltTexture::ET_modulate: + egg_texture->set_env_type(EggTexture::ET_modulate); + break; + + case FltTexture::ET_decal: + egg_texture->set_env_type(EggTexture::ET_decal); + break; + + case FltTexture::ET_blend: + case FltTexture::ET_color: + // Not supported. + break; + } + + switch (flt_texture->_internal_format) { + case FltTexture::IF_default: + break; + + case FltTexture::IF_i_12a_4: + case FltTexture::IF_ia_12: + case FltTexture::IF_ia_8: + egg_texture->set_format(EggTexture::F_luminance_alpha); + break; + + case FltTexture::IF_rgb_5: + egg_texture->set_format(EggTexture::F_rgb5); + break; + + case FltTexture::IF_rgba_4: + egg_texture->set_format(EggTexture::F_rgba4); + break; + + + case FltTexture::IF_rgba_8: + egg_texture->set_format(EggTexture::F_rgba8); + break; + + case FltTexture::IF_rgba_12: + egg_texture->set_format(EggTexture::F_rgba12); + break; + + case FltTexture::IF_i_16: + if (flt_texture->_intensity_is_alpha) { + egg_texture->set_format(EggTexture::F_alpha); + } else { + egg_texture->set_format(EggTexture::F_luminance); + } + break; + + case FltTexture::IF_rgb_12: + egg_texture->set_format(EggTexture::F_rgb12); + break; + } + + parse_comment(flt_texture, egg_texture); + return egg_texture; +} diff --git a/pandatool/src/fltegg/fltToEggConverter.h b/pandatool/src/fltegg/fltToEggConverter.h new file mode 100644 index 00000000..c9e8cc61 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggConverter.h @@ -0,0 +1,111 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggConverter.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef FLTTOEGGCONVERTER_H +#define FLTTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "fltToEggLevelState.h" +#include "somethingToEggConverter.h" +#include "fltHeader.h" +#include "eggVertex.h" +#include "eggVertexPool.h" +#include "eggTexture.h" +#include "pt_EggTexture.h" +#include "pt_EggVertex.h" +#include "pointerTo.h" +#include "distanceUnit.h" + +class FltRecord; +class FltLOD; +class FltGroup; +class FltObject; +class FltBeadID; +class FltBead; +class FltVertex; +class FltGeometry; +class FltFace; +class FltExternalReference; +class FltTexture; +class EggGroupNode; +class EggPrimitive; + +/** + * This class supervises the construction of an EggData structure from the + * data represented by the FltHeader. Reading and writing the egg and flt + * structures is left to the user. + */ +class FltToEggConverter : public SomethingToEggConverter { +public: + FltToEggConverter(); + FltToEggConverter(const FltToEggConverter ©); + ~FltToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + + virtual bool convert_file(const Filename &filename); + virtual DistanceUnit get_input_units(); + bool convert_flt(const FltHeader *flt_header); + + // Set this true to store transforms in egg files as the fully composed + // matrix, or false (the default) to keep them decomposed into elemental + // operations. + bool _compose_transforms; + +private: + void cleanup(); + + typedef pvector< PT_EggVertex > EggVertices; + + void convert_record(const FltRecord *flt_record, FltToEggLevelState &state); + void dispatch_record(const FltRecord *flt_record, FltToEggLevelState &state); + void convert_lod(const FltLOD *flt_lod, FltToEggLevelState &state); + void convert_group(const FltGroup *flt_group, FltToEggLevelState &state); + void convert_object(const FltObject *flt_object, FltToEggLevelState &state); + void convert_bead_id(const FltBeadID *flt_bead, FltToEggLevelState &state); + void convert_bead(const FltBead *flt_bead, FltToEggLevelState &state); + void convert_face(const FltFace *flt_face, FltToEggLevelState &state); + void convert_ext_ref(const FltExternalReference *flt_ext, FltToEggLevelState &state); + + void setup_geometry(const FltGeometry *flt_geom, FltToEggLevelState &state, + EggPrimitive *egg_prim, EggVertexPool *egg_vpool, + const EggVertices &vertices); + + void convert_subfaces(const FltRecord *flt_record, FltToEggLevelState &state); + + bool parse_comment(const FltBeadID *flt_bead, EggNode *egg_node); + bool parse_comment(const FltBead *flt_bead, EggNode *egg_node); + bool parse_comment(const FltTexture *flt_texture, EggNode *egg_node); + bool parse_comment(const std::string &comment, const std::string &name, + EggNode *egg_node); + + PT_EggVertex make_egg_vertex(const FltVertex *flt_vertex); + PT_EggTexture make_egg_texture(const FltTexture *flt_texture); + + CPT(FltHeader) _flt_header; + DistanceUnit _flt_units; + + PT(EggVertexPool) _main_egg_vpool; + + typedef pmap Textures; + Textures _textures; +}; + +#include "fltToEggConverter.I" + +#endif diff --git a/pandatool/src/fltegg/fltToEggLevelState.I b/pandatool/src/fltegg/fltToEggLevelState.I new file mode 100644 index 00000000..e5460717 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggLevelState.I @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggLevelState.I + * @author drose + * @date 2001-04-18 + */ + +/** + * + */ +INLINE FltToEggLevelState:: +FltToEggLevelState(FltToEggConverter *converter) : + _converter(converter) +{ + _flt_object = nullptr; + _egg_parent = nullptr; +} + +/** + * + */ +INLINE FltToEggLevelState:: +FltToEggLevelState(const FltToEggLevelState ©) : + _flt_object(copy._flt_object), + _egg_parent(copy._egg_parent), + _converter(copy._converter) +{ + // We don't bother to copy the _parents map. +} + +/** + * + */ +INLINE void FltToEggLevelState:: +operator = (const FltToEggLevelState ©) { + _flt_object = copy._flt_object; + _egg_parent = copy._egg_parent; + _converter = copy._converter; + // We don't bother to copy the _parents map. +} diff --git a/pandatool/src/fltegg/fltToEggLevelState.cxx b/pandatool/src/fltegg/fltToEggLevelState.cxx new file mode 100644 index 00000000..ef396fd8 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggLevelState.cxx @@ -0,0 +1,229 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggLevelState.cxx + * @author drose + * @date 2001-04-18 + */ + +#include "fltToEggLevelState.h" +#include "fltToEggConverter.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateAboutPoint.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformScale.h" +#include "fltTransformPut.h" +#include "eggGroup.h" +#include "dcast.h" +#include "look_at.h" + + +/** + * + */ +FltToEggLevelState:: +~FltToEggLevelState() { + Parents::iterator pi; + for (pi = _parents.begin(); pi != _parents.end(); ++pi) { + delete (*pi).second; + } +} + +/** + * + */ +FltToEggLevelState::ParentNodes:: +ParentNodes() { + _axial_billboard = nullptr; + _point_billboard = nullptr; + _plain = nullptr; +} + +/** + * Sometimes it is necessary to synthesize a group within a particular + * EggGroup, for instance to insert a transform or billboard flag. This + * function will synthesize a group as needed, or return an existing group (if + * the group need not be synthesized, or if a matching group was previously + * synthesized). + * + * This collects together polygons that share the same billboard axis and/or + * transform space into the same group, rather than wastefully creating a + * group per polygon. + */ +EggGroupNode *FltToEggLevelState:: +get_synthetic_group(const std::string &name, + const FltBead *transform_bead, + FltGeometry::BillboardType type) { + LMatrix4d transform = transform_bead->get_transform(); + bool is_identity = transform.almost_equal(LMatrix4d::ident_mat()); + if (is_identity && + (type != FltGeometry::BT_axial && + type != FltGeometry::BT_point)) { + // Trivial case: the primitive belongs directly in its parent group node. + return _egg_parent; + } + + // For other cases, we may have to create a subgroup to put the primitive + // into. + Parents::iterator pi; + pi = _parents.find(transform); + ParentNodes *nodes; + if (pi != _parents.end()) { + nodes = (*pi).second; + } else { + nodes = new ParentNodes; + _parents.insert(Parents::value_type(transform, nodes)); + } + + switch (type) { + case FltGeometry::BT_axial: + if (nodes->_axial_billboard == nullptr) { + nodes->_axial_billboard = new EggGroup(name); + _egg_parent->add_child(nodes->_axial_billboard); + nodes->_axial_billboard->set_billboard_type(EggGroup::BT_axis); + if (!is_identity) { + set_transform(transform_bead, nodes->_axial_billboard); + nodes->_axial_billboard->set_group_type(EggGroup::GT_instance); + } + } + return nodes->_axial_billboard; + + case FltGeometry::BT_point: + if (nodes->_point_billboard == nullptr) { + nodes->_point_billboard = new EggGroup(name); + _egg_parent->add_child(nodes->_point_billboard); + nodes->_point_billboard->set_billboard_type(EggGroup::BT_point_world_relative); + if (!is_identity) { + set_transform(transform_bead, nodes->_point_billboard); + nodes->_point_billboard->set_group_type(EggGroup::GT_instance); + } + } + return nodes->_point_billboard; + + default: // Normally, BT_none, although we've occasionally seen a + // value of 3 come in here, whatever that's supposed to mean. + if (nodes->_plain == nullptr) { + nodes->_plain = new EggGroup(name); + _egg_parent->add_child(nodes->_plain); + if (!is_identity) { + set_transform(transform_bead, nodes->_plain); + nodes->_plain->set_group_type(EggGroup::GT_instance); + } + } + return nodes->_plain; + } +} + +/** + * Sets up the group to reflect the transform indicated by the given record, + * if any. + */ +void FltToEggLevelState:: +set_transform(const FltBead *flt_bead, EggGroup *egg_group) { + if (flt_bead->has_transform()) { + egg_group->set_group_type(EggGroup::GT_instance); + + int num_steps = flt_bead->get_num_transform_steps(); + bool componentwise_ok = !_converter->_compose_transforms; + + if (num_steps == 0) { + componentwise_ok = false; + } else { + // Walk through each transform step and store the individual components + // in the egg file. If we come across a step we don't know how to + // interpret, just store the whole transform matrix in the egg file. + egg_group->clear_transform(); + for (int i = num_steps -1; i >= 0 && componentwise_ok; i--) { + const FltTransformRecord *step = flt_bead->get_transform_step(i); + if (step->is_exact_type(FltTransformTranslate::get_class_type())) { + const FltTransformTranslate *trans; + DCAST_INTO_V(trans, step); + if (!trans->get_delta().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(trans->get_delta()); + } + + } else if (step->is_exact_type(FltTransformRotateAboutPoint::get_class_type())) { + const FltTransformRotateAboutPoint *rap; + DCAST_INTO_V(rap, step); + if (!IS_NEARLY_ZERO(rap->get_angle())) { + if (!rap->get_center().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(-rap->get_center()); + } + LVector3d axis = LCAST(double, rap->get_axis()); + egg_group->add_rotate3d(rap->get_angle(), axis); + if (!rap->get_center().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(rap->get_center()); + } + } + + } else if (step->is_exact_type(FltTransformRotateAboutEdge::get_class_type())) { + const FltTransformRotateAboutEdge *rae; + DCAST_INTO_V(rae, step); + if (!IS_NEARLY_ZERO(rae->get_angle())) { + if (!rae->get_point_a().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(-rae->get_point_a()); + } + LVector3d axis = rae->get_point_b() - rae->get_point_a(); + egg_group->add_rotate3d(rae->get_angle(), axis); + if (!rae->get_point_a().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(rae->get_point_a()); + } + } + + } else if (step->is_exact_type(FltTransformScale::get_class_type())) { + const FltTransformScale *scale; + DCAST_INTO_V(scale, step); + if (!scale->get_scale().almost_equal(LVecBase3(1.0f, 1.0f, 1.0f))) { + if (scale->has_center() && + !scale->get_center().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(-scale->get_center()); + } + egg_group->add_scale3d(LCAST(double, scale->get_scale())); + if (scale->has_center() && + !scale->get_center().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(scale->get_center()); + } + } + + } else if (step->is_exact_type(FltTransformPut::get_class_type())) { + const FltTransformPut *put; + DCAST_INTO_V(put, step); + + if (!put->get_from_origin().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(-put->get_from_origin()); + } + LQuaterniond q1, q2; + look_at(q1, put->get_from_align() - put->get_from_origin(), + put->get_from_track() - put->get_from_origin(), + CS_zup_right); + look_at(q2, put->get_to_align() - put->get_to_origin(), + put->get_to_track() - put->get_to_origin(), + CS_zup_right); + + LQuaterniond q = invert(q1) * q2; + + if (!q.is_identity()) { + egg_group->add_rotate3d(q); + } + if (!put->get_to_origin().almost_equal(LVector3d::zero())) { + egg_group->add_translate3d(put->get_to_origin()); + } + + } else { + // Here's a transform component we haven't implemented here. Give + // up on storing the componentwise transform. + componentwise_ok = false; + } + } + } + + if (!componentwise_ok) { + egg_group->set_transform3d(flt_bead->get_transform()); + } + } +} diff --git a/pandatool/src/fltegg/fltToEggLevelState.h b/pandatool/src/fltegg/fltToEggLevelState.h new file mode 100644 index 00000000..91877e23 --- /dev/null +++ b/pandatool/src/fltegg/fltToEggLevelState.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEggLevelState.h + * @author drose + * @date 2001-04-18 + */ + +#ifndef FLTTOEGGLEVELSTATE_H +#define FLTTOEGGLEVELSTATE_H + +#include "pandatoolbase.h" +#include "fltGeometry.h" + +class FltObject; +class FltBead; +class EggGroupNode; +class EggGroup; +class FltToEggConverter; + +/** + * This keeps track of relevant things about the traversal as we walk through + * the flt hierarchy. + */ +class FltToEggLevelState { +public: + INLINE FltToEggLevelState(FltToEggConverter *converter); + INLINE FltToEggLevelState(const FltToEggLevelState ©); + INLINE void operator = (const FltToEggLevelState ©); + ~FltToEggLevelState(); + + EggGroupNode *get_synthetic_group(const std::string &name, + const FltBead *transform_bead, + FltGeometry::BillboardType type = FltGeometry::BT_none); + + void set_transform(const FltBead *flt_bead, EggGroup *egg_group); + + const FltObject *_flt_object; + EggGroupNode *_egg_parent; + +private: + class ParentNodes { + public: + ParentNodes(); + + EggGroup *_axial_billboard; + EggGroup *_point_billboard; + EggGroup *_plain; + }; + + typedef pmap Parents; + Parents _parents; + + FltToEggConverter *_converter; +}; + +#include "fltToEggLevelState.I" + +#endif diff --git a/pandatool/src/fltprogs/CMakeLists.txt b/pandatool/src/fltprogs/CMakeLists.txt new file mode 100644 index 00000000..0ceebcf6 --- /dev/null +++ b/pandatool/src/fltprogs/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(flt-info fltInfo.cxx fltInfo.h) +target_link_libraries(flt-info p3progbase p3flt) +install(TARGETS flt-info EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(flt-trans fltTrans.cxx fltTrans.h) +target_link_libraries(flt-trans p3progbase p3flt) +install(TARGETS flt-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(egg2flt eggToFlt.cxx eggToFlt.h) + target_link_libraries(egg2flt p3fltegg p3eggbase p3progbase) + install(TARGETS egg2flt EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(flt2egg fltToEgg.cxx fltToEgg.h) + target_link_libraries(flt2egg p3fltegg p3eggbase p3progbase) + install(TARGETS flt2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/fltprogs/eggToFlt.cxx b/pandatool/src/fltprogs/eggToFlt.cxx new file mode 100644 index 00000000..f6cbc9d2 --- /dev/null +++ b/pandatool/src/fltprogs/eggToFlt.cxx @@ -0,0 +1,672 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToFlt.cxx + * @author drose + * @date 2003-10-01 + */ + +#include "eggToFlt.h" +#include "fltHeader.h" +#include "fltBead.h" +#include "fltGroup.h" +#include "fltFace.h" +#include "fltVertexList.h" +#include "fltVertex.h" +#include "fltTexture.h" +#include "fltTransformTranslate.h" +#include "fltTransformRotateAboutEdge.h" +#include "fltTransformScale.h" +#include "fltTransformGeneralMatrix.h" +#include "eggPolygon.h" +#include "eggPoint.h" +#include "eggPrimitive.h" +#include "eggExternalReference.h" +#include "eggGroup.h" +#include "eggGroupNode.h" +#include "eggTexture.h" +#include "eggTransform.h" +#include "dcast.h" +#include "string_utils.h" +#include "vector_string.h" + +/** + * + */ +EggToFlt:: +EggToFlt() : + EggToSomething("MultiGen", ".flt", true, false) +{ + set_binary_output(true); + set_program_brief("convert files from .egg format to MultiGen .flt"); + set_program_description + ("egg2lt converts files from egg format to MultiGen .flt " + "format. It attempts to be as robust as possible, and matches " + "the capabilities of flt2egg. Generally, converting a model " + "from egg2lt and then back via flt2egg will result in essentially " + "the same egg file, within the limitations of what can be " + "represented in flt."); + + add_option + ("attr", "none/new/all", 0, + "Specifies whether to write (or rewrite) .attr files for each " + "texture image. MultiGen stores texture properties like mipmapping " + "in a separate .attr file for each different texture image. " + "If this parameter is \"none\", these files will not be generated; " + "if this is \"new\", these files will only be generated if they " + "do not already exist (even if the properties have changed). " + "Specifying \"all\" causes these to be rewritten every time.", + &EggToFlt::dispatch_attr, nullptr, &_auto_attr_update); + + // Flt files are always in the z-up coordinate system. Don't confuse the + // user with this meaningless option. + remove_option("cs"); + _coordinate_system = CS_zup_right; + _got_coordinate_system = true; + _auto_attr_update = FltHeader::AU_if_missing; +} + +/** + * + */ +void EggToFlt:: +run() { + _flt_header = new FltHeader(_path_replace); + _flt_header->set_auto_attr_update(_auto_attr_update); + + traverse(_data, _flt_header, FltGeometry::BT_none); + + // Finally, write the resulting file out. + FltError result = _flt_header->write_flt(get_output()); + if (result != FE_ok) { + nout << "Cannot write " << get_output_filename() << "\n"; + exit(1); + } +} + +/** + * Dispatch function for the -attr parameter. + */ +bool EggToFlt:: +dispatch_attr(const std::string &opt, const std::string &arg, void *var) { + FltHeader::AttrUpdate *ip = (FltHeader::AttrUpdate *)var; + + if (cmp_nocase(arg, "none") == 0) { + *ip = FltHeader::AU_none; + + } else if (cmp_nocase(arg, "new") == 0) { + *ip = FltHeader::AU_if_missing; + + } else if (cmp_nocase(arg, "all") == 0) { + *ip = FltHeader::AU_always; + + } else { + nout << "-" << opt + << " requires either \"none\", \"new\", or \"all\".\n"; + return false; + } + + return true; +} + +/** + * + */ +void EggToFlt:: +traverse(EggNode *egg_node, FltBead *flt_node, + FltGeometry::BillboardType billboard) { + if (egg_node->is_of_type(EggPolygon::get_class_type()) || + egg_node->is_of_type(EggPoint::get_class_type())) { + // It's a polygon or point light. + EggPrimitive *egg_primitive = DCAST(EggPrimitive, egg_node); + convert_primitive(egg_primitive, flt_node, billboard); + + } else if (egg_node->is_of_type(EggExternalReference::get_class_type())) { + // Convert external references. + + } else if (egg_node->is_of_type(EggGroup::get_class_type())) { + // An EggGroup creates a fltBead, and recurses. + EggGroup *egg_group = DCAST(EggGroup, egg_node); + + if (egg_group->get_group_type() == EggGroup::GT_joint) { + // Ignore joints and their children. + return; + } + + convert_group(egg_group, flt_node, billboard); + + } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + // Some kind of grouping node other than an EggGroup. Just recurse. + EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); + EggGroupNode::iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + traverse(*ci, flt_node, billboard); + } + } +} + +/** + * Converts an egg polygon or series of light points to the corresponding Flt + * geometry, and adds it to the indicated flt_node. + */ +void EggToFlt:: +convert_primitive(EggPrimitive *egg_primitive, FltBead *flt_node, + FltGeometry::BillboardType billboard) { + FltFace *flt_face = new FltFace(_flt_header); + flt_node->add_child(flt_face); + + flt_face->_billboard_type = billboard; + + if (egg_primitive->has_color()) { + flt_face->set_color(egg_primitive->get_color()); + } + + if (egg_primitive->is_of_type(EggPoint::get_class_type())) { + // A series of points, instead of a polygon. + flt_face->_draw_type = FltFace::DT_omni_light; + + } else if (egg_primitive->get_bface_flag()) { + // A polygon whose backface is visible. + flt_face->_draw_type = FltFace::DT_solid_no_cull; + + } else { + // A normal polygon. + flt_face->_draw_type = FltFace::DT_solid_cull_backface; + } + + if (egg_primitive->has_texture()) { + EggTexture *egg_texture = egg_primitive->get_texture(); + FltTexture *flt_texture = get_flt_texture(egg_texture); + flt_face->set_texture(flt_texture); + } + + // Create a vertex list representing the vertices in the primitive, and add + // it as a child of the face bead. This is how Flt files associate vertices + // with faces. + FltVertexList *flt_vertices = new FltVertexList(_flt_header); + flt_face->add_child(flt_vertices); + + EggPrimitive::iterator vi; + bool all_verts_have_color = true; + bool all_verts_have_normal = true; + for (vi = egg_primitive->begin(); vi != egg_primitive->end(); ++vi) { + EggVertex *egg_vertex = (*vi); + FltVertex *flt_vertex = get_flt_vertex(egg_vertex, egg_primitive); + flt_vertices->add_vertex(flt_vertex); + + if (!egg_vertex->has_color()) { + all_verts_have_color = false; + } + if (!egg_vertex->has_normal()) { + all_verts_have_normal = false; + } + } + if (all_verts_have_color) { + // If all the vertices of the face have a color specification, then we + // specify per-vertex color on the face. + if (all_verts_have_normal) { + // And similarly with the normals. + flt_face->_light_mode = FltFace::LM_vertex_with_normal; + } else { + flt_face->_light_mode = FltFace::LM_vertex_no_normal; + } + } else { + if (all_verts_have_normal) { + flt_face->_light_mode = FltFace::LM_face_with_normal; + } else { + flt_face->_light_mode = FltFace::LM_face_no_normal; + } + } +} + +/** + * Converts an egg group to the corresponding flt group, and adds it to the + * indicated parent node. Also recurses on the children of the egg group. + */ +void EggToFlt:: +convert_group(EggGroup *egg_group, FltBead *flt_node, + FltGeometry::BillboardType billboard) { + std::ostringstream egg_syntax; + + FltGroup *flt_group = new FltGroup(_flt_header); + flt_node->add_child(flt_group); + + flt_group->set_id(egg_group->get_name()); + + switch (egg_group->get_billboard_type()) { + // MultiGen represents billboarding at the polygon level, so we have to + // remember this flag for later. + case EggGroup::BT_axis: + billboard = FltGeometry::BT_axial; + break; + + case EggGroup::BT_point_world_relative: + billboard = FltGeometry::BT_point; + break; + + case EggGroup::BT_point_camera_relative: + // Not sure if this is the right flag for MultiGen. + billboard = FltGeometry::BT_fixed; + break; + + default: + break; + } + + if (egg_group->has_transform()) { + apply_transform(egg_group, flt_group); + } + + if (egg_group->get_switch_flag()) { + if (egg_group->get_switch_fps() != 0.0) { + // A sequence animation. + flt_group->_flags |= FltGroup::F_forward_animation; + egg_syntax + << " fps { " << egg_group->get_switch_fps() << " }\n"; + } else { + // Just a switch node. + egg_group->write_switch_flags(egg_syntax, 2); + } + } + + // Pick up any additional egg attributes that MultiGen doesn't support; + // these will get written to the comment field where flt2egg will find it. + egg_group->write_collide_flags(egg_syntax, 2); + egg_group->write_model_flags(egg_syntax, 2); + egg_group->write_object_types(egg_syntax, 2); + egg_group->write_decal_flags(egg_syntax, 2); + egg_group->write_tags(egg_syntax, 2); + egg_group->write_render_mode(egg_syntax, 2); + + apply_egg_syntax(egg_syntax.str(), flt_group); + + EggGroup::iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + traverse(*ci, flt_group, billboard); + } +} + +/** + * Applies the indicated egg transform to the indicated flt bead. + */ +void EggToFlt:: +apply_transform(EggTransform *egg_transform, FltBead *flt_node) { + flt_node->clear_transform(); + + bool components_ok = true; + int num_components = egg_transform->get_num_components(); + for (int i = num_components - 1; i >= 0 && components_ok; i--) { + switch (egg_transform->get_component_type(i)) { + case EggTransform::CT_translate2d: + { + FltTransformTranslate *translate = + new FltTransformTranslate(_flt_header); + LVector2d v2 = egg_transform->get_component_vec2(i); + translate->set(LPoint3d::zero(), LVector3d(v2[0], v2[1], 0.0)); + flt_node->add_transform_step(translate); + } + break; + + case EggTransform::CT_translate3d: + { + FltTransformTranslate *translate = + new FltTransformTranslate(_flt_header); + translate->set(LPoint3d::zero(), egg_transform->get_component_vec3(i)); + flt_node->add_transform_step(translate); + } + break; + + case EggTransform::CT_rotate2d: + { + FltTransformRotateAboutEdge *rotate = + new FltTransformRotateAboutEdge(_flt_header); + rotate->set(LPoint3d::zero(), LPoint3d(0.0, 0.0, 1.0), + egg_transform->get_component_number(i)); + flt_node->add_transform_step(rotate); + } + break; + + case EggTransform::CT_rotx: + { + FltTransformRotateAboutEdge *rotate = + new FltTransformRotateAboutEdge(_flt_header); + rotate->set(LPoint3d::zero(), LPoint3d(1.0, 0.0, 0.0), + egg_transform->get_component_number(i)); + flt_node->add_transform_step(rotate); + } + break; + + case EggTransform::CT_roty: + { + FltTransformRotateAboutEdge *rotate = + new FltTransformRotateAboutEdge(_flt_header); + rotate->set(LPoint3d::zero(), LPoint3d(0.0, 1.0, 0.0), + egg_transform->get_component_number(i)); + flt_node->add_transform_step(rotate); + } + break; + + case EggTransform::CT_rotz: + { + FltTransformRotateAboutEdge *rotate = + new FltTransformRotateAboutEdge(_flt_header); + rotate->set(LPoint3d::zero(), LPoint3d(0.0, 0.0, 1.0), + egg_transform->get_component_number(i)); + flt_node->add_transform_step(rotate); + } + break; + + case EggTransform::CT_rotate3d: + { + FltTransformRotateAboutEdge *rotate = + new FltTransformRotateAboutEdge(_flt_header); + rotate->set(LPoint3d::zero(), egg_transform->get_component_vec3(i), + egg_transform->get_component_number(i)); + flt_node->add_transform_step(rotate); + } + break; + + case EggTransform::CT_scale2d: + { + FltTransformScale *scale = new FltTransformScale(_flt_header); + LVector2d v2 = egg_transform->get_component_vec2(i); + scale->set(LPoint3d::zero(), LVector3(v2[0], v2[1], 1.0f)); + flt_node->add_transform_step(scale); + } + break; + + case EggTransform::CT_scale3d: + { + FltTransformScale *scale = new FltTransformScale(_flt_header); + scale->set(LPoint3d::zero(), LCAST(PN_stdfloat, egg_transform->get_component_vec3(i))); + flt_node->add_transform_step(scale); + } + break; + + case EggTransform::CT_uniform_scale: + { + FltTransformScale *scale = new FltTransformScale(_flt_header); + PN_stdfloat factor = (PN_stdfloat)egg_transform->get_component_number(i); + scale->set(LPoint3d::zero(), LVecBase3(factor, factor, factor)); + flt_node->add_transform_step(scale); + } + break; + + case EggTransform::CT_matrix3: + { + FltTransformGeneralMatrix *matrix = + new FltTransformGeneralMatrix(_flt_header); + const LMatrix3d &m = egg_transform->get_component_mat3(i); + LMatrix4d mat4(m(0, 0), m(0, 1), 0.0, m(0, 2), + m(1, 0), m(1, 1), 0.0, m(1, 2), + 0.0, 0.0, 1.0, 0.0, + m(2, 0), m(2, 1), 0.0, m(2, 2)); + matrix->set_matrix(mat4); + flt_node->add_transform_step(matrix); + } + break; + + case EggTransform::CT_matrix4: + { + FltTransformGeneralMatrix *matrix = + new FltTransformGeneralMatrix(_flt_header); + matrix->set_matrix(egg_transform->get_component_mat4(i)); + flt_node->add_transform_step(matrix); + } + break; + + default: + // Don't know how to convert this component. + components_ok = false; + } + } + + if (components_ok) { + // Verify that the transform was computed correctly. + if (!flt_node->get_transform().almost_equal(egg_transform->get_transform3d())) { + nout << "Incorrect transform! Expected:\n"; + egg_transform->get_transform3d().write(nout, 2); + nout << "Computed:\n"; + flt_node->get_transform().write(nout, 2); + nout << "\n"; + components_ok = false; + } + } + + if (!components_ok) { + // Just store the overall transform. + flt_node->set_transform(egg_transform->get_transform3d()); + } +} + +/** + * Adds the indicated sequence of egg syntax lines (presumably representing + * egg features not directly supported by MultiGen) to the flt record as a + * comment, so that flt2egg will reapply it to the egg groups. + */ +void EggToFlt:: +apply_egg_syntax(const std::string &egg_syntax, FltRecord *flt_record) { + if (!egg_syntax.empty()) { + std::ostringstream out; + out << " {\n" + << egg_syntax + << "}"; + flt_record->set_comment(out.str()); + } +} + +/** + * Returns a FltVertex corresponding to the indicated EggVertex. If the + * vertex has not been seen before (in this particular vertex frame), creates + * a new one. + */ +FltVertex *EggToFlt:: +get_flt_vertex(EggVertex *egg_vertex, EggNode *context) { + const LMatrix4d *frame = context->get_vertex_to_node_ptr(); + VertexMap &vertex_map = _vertex_map_per_frame[frame]; + + VertexMap::iterator vi = vertex_map.find(egg_vertex); + if (vi != vertex_map.end()) { + return (*vi).second; + } + FltVertex *flt_vertex = new FltVertex(_flt_header); + flt_vertex->_pos = egg_vertex->get_pos3(); + + if (egg_vertex->has_color()) { + flt_vertex->set_color(egg_vertex->get_color()); + } + if (egg_vertex->has_normal()) { + flt_vertex->_normal = LCAST(PN_stdfloat, egg_vertex->get_normal()); + flt_vertex->_has_normal = true; + } + if (egg_vertex->has_uv()) { + flt_vertex->_uv = LCAST(PN_stdfloat, egg_vertex->get_uv()); + flt_vertex->_has_uv = true; + } + + if (frame != nullptr) { + flt_vertex->_pos = flt_vertex->_pos * (*frame); + flt_vertex->_normal = flt_vertex->_normal * LCAST(PN_stdfloat, (*frame)); + } + + _flt_header->add_vertex(flt_vertex); + vertex_map[egg_vertex] = flt_vertex; + + return flt_vertex; +} + +/** + * Returns a FltTexture corresponding to the indicated EggTexture. If the + * texture has not been seen before, creates a new one. + */ +FltTexture *EggToFlt:: +get_flt_texture(EggTexture *egg_texture) { + // We have to maintain this map based on the filename, not the egg pointer, + // because there may be multiple EggTextures with the same filename, and we + // have to collapse them together. + Filename filename = egg_texture->get_filename(); + TextureMap::iterator vi = _texture_map.find(filename); + if (vi != _texture_map.end()) { + return (*vi).second; + } + FltTexture *flt_texture = new FltTexture(_flt_header); + flt_texture->set_texture_filename(filename); + + switch (egg_texture->get_minfilter()) { + case EggTexture::FT_nearest: + flt_texture->_min_filter = FltTexture::MN_point; + break; + + case EggTexture::FT_linear: + flt_texture->_min_filter = FltTexture::MN_bilinear; + break; + + case EggTexture::FT_nearest_mipmap_nearest: + flt_texture->_min_filter = FltTexture::MN_mipmap_point; + break; + + case EggTexture::FT_nearest_mipmap_linear: + flt_texture->_min_filter = FltTexture::MN_mipmap_linear; + break; + + case EggTexture::FT_linear_mipmap_nearest: + flt_texture->_min_filter = FltTexture::MN_mipmap_bilinear; + break; + + case EggTexture::FT_linear_mipmap_linear: + flt_texture->_min_filter = FltTexture::MN_mipmap_trilinear; + break; + + default: + break; + } + + switch (egg_texture->get_magfilter()) { + case EggTexture::FT_nearest: + flt_texture->_mag_filter = FltTexture::MG_point; + break; + + case EggTexture::FT_linear: + flt_texture->_mag_filter = FltTexture::MG_bilinear; + break; + + default: + break; + } + + switch (egg_texture->get_wrap_mode()) { + case EggTexture::WM_repeat: + flt_texture->_repeat = FltTexture::RT_repeat; + break; + + case EggTexture::WM_clamp: + flt_texture->_repeat = FltTexture::RT_clamp; + break; + + default: + break; + } + + switch (egg_texture->get_wrap_u()) { + case EggTexture::WM_repeat: + flt_texture->_repeat_u = FltTexture::RT_repeat; + break; + + case EggTexture::WM_clamp: + flt_texture->_repeat_u = FltTexture::RT_clamp; + break; + + default: + break; + } + + switch (egg_texture->get_wrap_v()) { + case EggTexture::WM_repeat: + flt_texture->_repeat_v = FltTexture::RT_repeat; + break; + + case EggTexture::WM_clamp: + flt_texture->_repeat_v = FltTexture::RT_clamp; + break; + + default: + break; + } + + switch (egg_texture->get_env_type()) { + case EggTexture::ET_modulate: + flt_texture->_env_type = FltTexture::ET_modulate; + break; + + case EggTexture::ET_decal: + flt_texture->_env_type = FltTexture::ET_decal; + break; + + default: + break; + } + + switch (egg_texture->get_format()) { + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + flt_texture->_internal_format = FltTexture::IF_ia_8; + break; + + case EggTexture::F_rgb5: + case EggTexture::F_rgb332: + flt_texture->_internal_format = FltTexture::IF_rgb_5; + break; + + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + flt_texture->_internal_format = FltTexture::IF_rgba_4; + break; + + case EggTexture::F_rgba8: + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgb: + case EggTexture::F_rgb8: + flt_texture->_internal_format = FltTexture::IF_rgba_8; + break; + + case EggTexture::F_rgba12: + flt_texture->_internal_format = FltTexture::IF_rgba_12; + break; + + case EggTexture::F_alpha: + flt_texture->_internal_format = FltTexture::IF_i_16; + flt_texture->_intensity_is_alpha = true; + break; + + case EggTexture::F_luminance: + flt_texture->_internal_format = FltTexture::IF_i_16; + break; + + case EggTexture::F_rgb12: + flt_texture->_internal_format = FltTexture::IF_rgb_12; + break; + + default: + break; + } + + _flt_header->add_texture(flt_texture); + _texture_map[filename] = flt_texture; + + return flt_texture; +} + + + +int main(int argc, char *argv[]) { + EggToFlt prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/fltprogs/eggToFlt.h b/pandatool/src/fltprogs/eggToFlt.h new file mode 100644 index 00000000..0cbcb212 --- /dev/null +++ b/pandatool/src/fltprogs/eggToFlt.h @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToFlt.h + * @author drose + * @date 2003-10-01 + */ + +#ifndef EGGTOFLT_H +#define EGGTOFLT_H + +#include "pandatoolbase.h" + +#include "eggToSomething.h" +#include "fltHeader.h" +#include "fltGeometry.h" +#include "pointerTo.h" +#include "pmap.h" +#include "vector_string.h" + +class EggGroup; +class EggVertex; +class EggPrimitive; +class EggTexture; +class EggTransform; +class FltVertex; +class FltBead; +class FltTexture; + +/** + * A program to read an egg file and write a flt file. + */ +class EggToFlt : public EggToSomething { +public: + EggToFlt(); + + void run(); + +private: + static bool dispatch_attr(const std::string &opt, const std::string &arg, void *var); + + void traverse(EggNode *egg_node, FltBead *flt_node, + FltGeometry::BillboardType billboard); + void convert_primitive(EggPrimitive *egg_primitive, FltBead *flt_node, + FltGeometry::BillboardType billboard); + void convert_group(EggGroup *egg_group, FltBead *flt_node, + FltGeometry::BillboardType billboard); + void apply_transform(EggTransform *egg_transform, FltBead *flt_node); + void apply_egg_syntax(const std::string &egg_syntax, FltRecord *flt_record); + FltVertex *get_flt_vertex(EggVertex *egg_vertex, EggNode *context); + FltTexture *get_flt_texture(EggTexture *egg_texture); + + FltHeader::AttrUpdate _auto_attr_update; + + PT(FltHeader) _flt_header; + + typedef pmap VertexMap; + typedef pmap VertexMapPerFrame; + VertexMapPerFrame _vertex_map_per_frame; + + typedef pmap TextureMap; + TextureMap _texture_map; +}; + +#endif diff --git a/pandatool/src/fltprogs/fltInfo.cxx b/pandatool/src/fltprogs/fltInfo.cxx new file mode 100644 index 00000000..bd0e9d8a --- /dev/null +++ b/pandatool/src/fltprogs/fltInfo.cxx @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInfo.cxx + * @author drose + * @date 2001-09-05 + */ + +#include "fltInfo.h" + +#include "fltHeader.h" +#include "indent.h" + +/** + * + */ +FltInfo:: +FltInfo() { + set_program_brief("describe the contents of a MultiGen .flt file"); + set_program_description + ("This program reads a MultiGen OpenFlight (.flt) file and reports " + "some interesting things about its contents."); + + clear_runlines(); + add_runline("[opts] input.flt"); + + add_option + ("ls", "", 0, + "List the hierarchy in the flt file.", + &FltInfo::dispatch_none, &_list_hierarchy); +} + + +/** + * + */ +void FltInfo:: +run() { + PT(FltHeader) header = new FltHeader(_path_replace); + + nout << "Reading " << _input_filename << "\n"; + FltError result = header->read_flt(_input_filename); + if (result != FE_ok) { + nout << "Unable to read: " << result << "\n"; + exit(1); + } + + if (header->check_version()) { + nout << "Version is " << header->get_flt_version() / 100.0 << "\n"; + } + + if (_list_hierarchy) { + list_hierarchy(header, 0); + } +} + +/** + * Recursively lists the flt file's hierarchy in a meaningful way. + */ +void FltInfo:: +list_hierarchy(FltRecord *record, int indent_level) { + // Maybe in the future we can do something fancier here. + record->write(std::cout, indent_level); +} + + +/** + * + */ +bool FltInfo:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the .flt file to read on the command line.\n"; + return false; + + } else if (args.size() != 1) { + nout << "You must specify only one .flt file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int main(int argc, char *argv[]) { + FltInfo prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/fltprogs/fltInfo.h b/pandatool/src/fltprogs/fltInfo.h new file mode 100644 index 00000000..1dc87953 --- /dev/null +++ b/pandatool/src/fltprogs/fltInfo.h @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltInfo.h + * @author drose + * @date 2001-09-05 + */ + +#ifndef FLTINFO_H +#define FLTINFO_H + +#include "pandatoolbase.h" + +#include "programBase.h" + +class FltRecord; + +/** + * A program to read a flt file and report interesting things about it. + */ +class FltInfo : public ProgramBase { +public: + FltInfo(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + void list_hierarchy(FltRecord *record, int indent_level); + + Filename _input_filename; + bool _list_hierarchy; +}; + +#endif diff --git a/pandatool/src/fltprogs/fltToEgg.cxx b/pandatool/src/fltprogs/fltToEgg.cxx new file mode 100644 index 00000000..c08a8381 --- /dev/null +++ b/pandatool/src/fltprogs/fltToEgg.cxx @@ -0,0 +1,106 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEgg.cxx + * @author drose + * @date 2001-04-17 + */ + +#include "fltToEgg.h" + +#include "fltToEggConverter.h" +#include "config_flt.h" + +/** + * + */ +FltToEgg:: +FltToEgg() : + SomethingToEgg("MultiGen", ".flt") +{ + add_path_replace_options(); + add_path_store_options(); + add_units_options(); + add_normals_options(); + add_transform_options(); + add_merge_externals_options(); + + set_program_brief("convert a MultiGen .flt file to .egg"); + set_program_description + ("This program converts MultiGen OpenFlight (.flt) files to egg. Most " + "features of MultiGen that are also recognized by egg are supported."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is z-up."); + + // Does anyone really care about this option? It's mainly useful for + // debugging the flt2egg logic. + /* + add_option + ("C", "", 0, + "Compose node transforms into a single matrix before writing them to " + "the egg file, instead of writing them as individual scale, rotate, and " + "translate operations.", + &FltToEgg::dispatch_none, &_compose_transforms); + */ + _compose_transforms = false; + + _coordinate_system = CS_zup_right; +} + +/** + * + */ +void FltToEgg:: +run() { + _data->set_coordinate_system(_coordinate_system); + + FltToEggConverter converter; + converter.set_merge_externals(_merge_externals); + converter.set_egg_data(_data); + converter._compose_transforms = _compose_transforms; + converter._allow_errors = _allow_errors; + + apply_parameters(converter); + + + PT(FltHeader) header = new FltHeader(_path_replace); + + nout << "Reading " << _input_filename << "\n"; + FltError result = header->read_flt(_input_filename); + if (result != FE_ok) { + nout << "Unable to read: " << result << "\n"; + exit(1); + } + + header->check_version(); + + + if (!converter.convert_flt(header)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + if (_input_units == DU_invalid) { + _input_units = converter.get_input_units(); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + init_libflt(); + FltToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/fltprogs/fltToEgg.h b/pandatool/src/fltprogs/fltToEgg.h new file mode 100644 index 00000000..6a756fe6 --- /dev/null +++ b/pandatool/src/fltprogs/fltToEgg.h @@ -0,0 +1,36 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltToEgg.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef FLTTOEGG_H +#define FLTTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "fltToEggConverter.h" + +#include "dSearchPath.h" + +/** + * A program to read a flt file and generate an egg file. + */ +class FltToEgg : public SomethingToEgg { +public: + FltToEgg(); + + void run(); + + bool _compose_transforms; +}; + +#endif diff --git a/pandatool/src/fltprogs/fltTrans.cxx b/pandatool/src/fltprogs/fltTrans.cxx new file mode 100644 index 00000000..692f8e37 --- /dev/null +++ b/pandatool/src/fltprogs/fltTrans.cxx @@ -0,0 +1,133 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTrans.cxx + * @author drose + * @date 2001-04-11 + */ + +#include "fltTrans.h" + +#include "fltHeader.h" + +/** + * + */ +FltTrans:: +FltTrans() : + WithOutputFile(true, false, true) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = ".flt"; + + set_program_brief("apply various operations to a MultiGen .flt file"); + set_program_description + ("This program reads a MultiGen OpenFlight (.flt) file and writes an " + "essentially equivalent .flt file, to the file specified with -o (or " + "as the second parameter). Some simple operations may be performed."); + + clear_runlines(); + add_runline("[opts] input.flt output.flt"); + add_runline("[opts] -o output.flt input.flt"); + + add_path_replace_options(); + add_path_store_options(); + + add_option + ("v", "version", 0, + "Upgrade (or downgrade) the flt file to the indicated version. This " + "may not be completely correct for all version-to-version combinations.", + &FltTrans::dispatch_double, &_got_new_version, &_new_version); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting .flt file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file.", + &FltTrans::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +/** + * + */ +void FltTrans:: +run() { + if (_got_new_version) { + int new_version = (int)floor(_new_version * 100.0 + 0.5); + if (new_version < FltHeader::min_flt_version() || + new_version > FltHeader::max_flt_version()) { + nout << "Cannot write flt files of version " << new_version / 100.0 + << ". This program only understands how to write flt files between version " + << FltHeader::min_flt_version() / 100.0 << " and " + << FltHeader::max_flt_version() / 100.0 << ".\n"; + exit(1); + } + } + + PT(FltHeader) header = new FltHeader(_path_replace); + + nout << "Reading " << _input_filename << "\n"; + FltError result = header->read_flt(_input_filename); + if (result != FE_ok) { + nout << "Unable to read: " << result << "\n"; + exit(1); + } + + if (header->check_version()) { + nout << "Version is " << header->get_flt_version() / 100.0 << "\n"; + } + + if (_got_new_version) { + int new_version = (int)floor(_new_version * 100.0 + 0.5); + header->set_flt_version(new_version); + } + + header->apply_converted_filenames(); + + result = header->write_flt(get_output()); + if (result != FE_ok) { + nout << "Unable to write: " << result << "\n"; + exit(1); + } + + nout << "Successfully written.\n"; +} + + +/** + * + */ +bool FltTrans:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + if (args.empty()) { + nout << "You must specify the .flt file to read on the command line.\n"; + return false; + + } else if (args.size() != 1) { + nout << "You must specify only one .flt file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int main(int argc, char *argv[]) { + FltTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/fltprogs/fltTrans.h b/pandatool/src/fltprogs/fltTrans.h new file mode 100644 index 00000000..042418f6 --- /dev/null +++ b/pandatool/src/fltprogs/fltTrans.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file fltTrans.h + * @author drose + * @date 2001-04-11 + */ + +#ifndef FLTTRANS_H +#define FLTTRANS_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "withOutputFile.h" + +/** + * A program to read a flt file and write an equivalent flt file, possibly + * performing some minor operations along the way. + */ +class FltTrans : public ProgramBase, public WithOutputFile { +public: + FltTrans(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; + bool _got_new_version; + double _new_version; +}; + +#endif diff --git a/pandatool/src/gtk-stats/CMakeLists.txt b/pandatool/src/gtk-stats/CMakeLists.txt new file mode 100644 index 00000000..dca2f914 --- /dev/null +++ b/pandatool/src/gtk-stats/CMakeLists.txt @@ -0,0 +1,47 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_GTK3 OR NOT HAVE_NET) + return() +endif() + +set(GTKSTATS_HEADERS + gtkStatsChartMenu.h + gtkStatsFlameGraph.h + gtkStatsGraph.h + gtkStatsLabel.h + gtkStatsLabelStack.h + gtkStatsMonitor.h gtkStatsMonitor.I + gtkStatsPianoRoll.h + gtkStatsServer.h + gtkStatsStripChart.h +) + +set(GTKSTATS_SOURCES + gtkStats.cxx + gtkStatsChartMenu.cxx + gtkStatsFlameGraph.cxx + gtkStatsGraph.cxx + gtkStatsLabel.cxx + gtkStatsLabelStack.cxx + gtkStatsMonitor.cxx + gtkStatsPianoRoll.cxx + gtkStatsServer.cxx + gtkStatsStripChart.cxx + gtkStatsTimeline.cxx +) + +composite_sources(gtk-stats GTKSTATS_SOURCES) +add_executable(gtk-stats ${GTKSTATS_HEADERS} ${GTKSTATS_SOURCES}) +target_link_libraries(gtk-stats p3progbase p3pstatserver PKG::GTK3) + +# This program is NOT actually called gtk-stats. It's pstats-gtk on Win32 and +# pstats everywhere else (as the Win32 GUI is not built). +if(WIN32 OR APPLE) + set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats-gtk") +else() + set_target_properties(gtk-stats PROPERTIES OUTPUT_NAME "pstats") +endif() + +install(TARGETS gtk-stats EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/gtk-stats/gtkStats.cxx b/pandatool/src/gtk-stats/gtkStats.cxx new file mode 100644 index 00000000..344af201 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStats.cxx @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStats.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "pandatoolbase.h" +#include "gtkStats.h" +#include "gtkStatsServer.h" +#include "config_pstatclient.h" + +int +main(int argc, char *argv[]) { + gtk_init(&argc, &argv); + + // Create the server window. + GtkStatsServer *server = new GtkStatsServer; + server->parse_command_line(argc, argv); + + // Now get lost in the message loop. + gtk_main(); + + return (0); +} diff --git a/pandatool/src/gtk-stats/gtkStats.h b/pandatool/src/gtk-stats/gtkStats.h new file mode 100644 index 00000000..a8cd1cb0 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStats.h @@ -0,0 +1,19 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStats.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATS_H +#define GTKSTATS_H + +#include "pStatServer.h" + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx b/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx new file mode 100644 index 00000000..7f0ad0a4 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsChartMenu.cxx @@ -0,0 +1,328 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsChartMenu.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsChartMenu.h" +#include "gtkStatsMonitor.h" + +/** + * + */ +GtkStatsChartMenu:: +GtkStatsChartMenu(GtkStatsMonitor *monitor, int thread_index) : + _monitor(monitor), + _thread_index(thread_index) +{ + _menu = gtk_menu_new(); + + if (thread_index == 0) { + // Timeline goes first. + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), + make_menu_item("Timeline", -1, GtkStatsMonitor::CT_timeline, false)); + + // Then the piano roll (even though it's not very useful nowadays) + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), + make_menu_item("Piano Roll", -1, GtkStatsMonitor::CT_piano_roll, false)); + } + else { + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), + make_menu_item("Open Strip Chart", 0, GtkStatsMonitor::CT_strip_chart, false)); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), + make_menu_item("Open Flame Graph", -1, GtkStatsMonitor::CT_flame_graph, false)); + } + + { + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_widget_show(sep); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep); + } + _time_items_end = 3; + + // Put a separator between time items and level items. + { + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_widget_show(sep); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep); + _level_items_end = _time_items_end + 1; + } + + // For the main thread menu, also some options relating to all graph windows. + if (thread_index == 0) { + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_widget_show(sep); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), sep); + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Close All Graphs"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_close_all), + (void *)_monitor); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reopen Default Graphs"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_reopen_default), + (void *)_monitor); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Save Current Layout as Default"); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(activate_save_default), + (void *)_monitor); + } + } + + do_update(); + gtk_widget_show(_menu); +} + +/** + * + */ +GtkStatsChartMenu:: +~GtkStatsChartMenu() { +} + +/** + * Returns the gtk widget for this particular menu. + */ +GtkWidget *GtkStatsChartMenu:: +get_menu_widget() { + return _menu; +} + +/** + * Adds the menu to the end of the indicated menu bar. + */ +void GtkStatsChartMenu:: +add_to_menu_bar(GtkWidget *menu_bar, int position) { + const PStatClientData *client_data = _monitor->get_client_data(); + std::string thread_name; + if (_thread_index == 0) { + // A special case for the main thread. + thread_name = "Graphs"; + } else { + thread_name = client_data->get_thread_name(_thread_index); + } + + _menu_item = gtk_menu_item_new_with_label(thread_name.c_str()); + gtk_widget_show(_menu_item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(_menu_item), _menu); + + gtk_menu_shell_insert(GTK_MENU_SHELL(menu_bar), _menu_item, position); +} + +/** + * Removes the menu from the menu bar. + */ +void GtkStatsChartMenu:: +remove_from_menu_bar(GtkWidget *menu_bar) { + gtk_container_remove(GTK_CONTAINER(menu_bar), _menu_item); +} + +/** + * Checks to see if the menu needs to be updated (e.g. because of new data + * from the client), and updates it if necessary. + */ +void GtkStatsChartMenu:: +check_update() { + PStatView &view = _monitor->get_view(_thread_index); + if (view.get_level_index() != _last_level_index) { + do_update(); + } +} + +/** + * Unconditionally updates the menu with the latest data from the client. + */ +void GtkStatsChartMenu:: +do_update() { + PStatView &view = _monitor->get_view(_thread_index); + _last_level_index = view.get_level_index(); + + const PStatClientData *client_data = _monitor->get_client_data(); + if ((size_t)client_data->get_num_collectors() > _collector_items.size()) { + _collector_items.resize(client_data->get_num_collectors(), std::make_pair(nullptr, nullptr)); + } + + // The menu item(s) for the thread's frame time goes second. + const PStatViewLevel *view_level = view.get_top_level(); + if (_thread_index == 0) { + if (add_view(_menu, view_level, false, _time_items_end)) { + ++_time_items_end; + ++_level_items_end; + } + } else { + for (int c = 0; c < view_level->get_num_children(); ++c) { + if (add_view(_menu, view_level->get_child(c), false, _time_items_end)) { + ++_time_items_end; + ++_level_items_end; + } + } + } + + // And then the menu item(s) for each of the level values. + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, _thread_index)) { + + PStatView &level_view = _monitor->get_level_view(collector, _thread_index); + add_view(_menu, level_view.get_top_level(), true, _level_items_end); + } + } +} + +/** + * Adds a new entry or entries to the menu for the indicated view and its + * children. Returns true if an item was added, false if not. + */ +bool GtkStatsChartMenu:: +add_view(GtkWidget *parent_menu, const PStatViewLevel *view_level, + bool show_level, int insert_at) { + int collector = view_level->get_collector(); + + GtkWidget *&menu_item = _collector_items[collector].first; + GtkWidget *&menu = _collector_items[collector].second; + + const PStatClientData *client_data = _monitor->get_client_data(); + + int num_children = view_level->get_num_children(); + if (menu == nullptr && num_children == 0) { + // For a collector without children, no point in making a submenu. We just + // have the item open a strip chart directly (no point in creating a flame + // graph if there are no children). + if (menu_item != nullptr) { + // Already exists. + return false; + } + + std::string collector_name = client_data->get_collector_name(collector); + menu_item = make_menu_item( + collector_name.c_str(), collector, GtkStatsMonitor::CT_strip_chart, show_level); + gtk_menu_shell_insert(GTK_MENU_SHELL(parent_menu), menu_item, insert_at); + return true; + } + else if (menu_item != nullptr && menu == nullptr) { + // Unhook the signal handler, we are creating a submenu. + GtkStatsMonitor::MenuDef smd(GtkStatsMonitor::CT_strip_chart, _thread_index, collector, -1, show_level); + const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); + + g_signal_handlers_disconnect_by_data(G_OBJECT(menu_item), (void *)menu_def); + } + + // Create a submenu. + bool added_item = false; + if (menu_item == nullptr) { + std::string collector_name = client_data->get_collector_name(collector); + menu_item = gtk_menu_item_new_with_label(collector_name.c_str()); + gtk_widget_show(menu_item); + gtk_menu_shell_insert(GTK_MENU_SHELL(parent_menu), menu_item, insert_at); + added_item = true; + } + + if (menu == nullptr) { + menu = gtk_menu_new(); + gtk_widget_show(menu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), + make_menu_item("Open Strip Chart", collector, GtkStatsMonitor::CT_strip_chart, show_level)); + + if (!show_level) { + if (collector == 0) { + collector = -1; + } + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), + make_menu_item("Open Flame Graph", collector, GtkStatsMonitor::CT_flame_graph)); + } + + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_widget_show(sep); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep); + } + + for (int c = 0; c < num_children; ++c) { + add_view(menu, view_level->get_child(c), show_level, 3 + !show_level); + } + + return added_item; +} + +/** + * + */ +GtkWidget *GtkStatsChartMenu:: +make_menu_item(const char *label, int collector_index, ChartType chart_type, + bool show_level) { + GtkStatsMonitor::MenuDef smd(chart_type, _thread_index, collector_index, -1, show_level); + const GtkStatsMonitor::MenuDef *menu_def = _monitor->add_menu(smd); + + GtkWidget *menu_item = gtk_menu_item_new_with_label(label); + gtk_widget_show(menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + + return menu_item; +} + +/** + * Removes a previous menu child from the menu. + */ +void GtkStatsChartMenu:: +remove_menu_child(GtkWidget *widget, gpointer data) { + GtkWidget *menu = (GtkWidget *)data; + gtk_container_remove(GTK_CONTAINER(menu), widget); +} + +/** + * Callback for Close All Graphs. + */ +void GtkStatsChartMenu:: +activate_close_all(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->remove_all_graphs(); +} + +/** + * Callback for Reopen Default Graphs. + */ +void GtkStatsChartMenu:: +activate_reopen_default(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->remove_all_graphs(); + monitor->open_default_graphs(); +} + +/** + * Callback for Save Current Layout as Default. + */ +void GtkStatsChartMenu:: +activate_save_default(GtkWidget *widget, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + monitor->save_default_graphs(); +} diff --git a/pandatool/src/gtk-stats/gtkStatsChartMenu.h b/pandatool/src/gtk-stats/gtkStatsChartMenu.h new file mode 100644 index 00000000..f2dfb6da --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsChartMenu.h @@ -0,0 +1,68 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsChartMenu.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSCHARTMENU_H +#define GTKSTATSCHARTMENU_H + +#include "pandatoolbase.h" +#include "gtkStatsMonitor.h" + +#include + +class PStatView; +class PStatViewLevel; + +/** + * A pulldown menu of charts available for a particular thread. + */ +class GtkStatsChartMenu { +public: + typedef GtkStatsMonitor::ChartType ChartType; + + GtkStatsChartMenu(GtkStatsMonitor *monitor, int thread_index); + ~GtkStatsChartMenu(); + + int get_thread_index() const { return _thread_index; } + + GtkWidget *get_menu_widget(); + void add_to_menu_bar(GtkWidget *menu_bar, int position); + void remove_from_menu_bar(GtkWidget *menu_bar); + + void check_update(); + void do_update(); + +private: + bool add_view(GtkWidget *parent_menu, const PStatViewLevel *view_level, + bool show_level, int insert_at); + GtkWidget *make_menu_item(const char *label, int collector_index, + ChartType chart_type, bool show_level = false); + + static void remove_menu_child(GtkWidget *widget, gpointer data); + static void activate_close_all(GtkWidget *widget, gpointer data); + static void activate_reopen_default(GtkWidget *widget, gpointer data); + static void activate_save_default(GtkWidget *widget, gpointer data); + + GtkStatsMonitor *_monitor; + int _thread_index; + + int _last_level_index; + GtkWidget *_menu; + GtkWidget *_menu_item = nullptr; + + // Pair of menu item, submenu + std::vector > _collector_items; + int _time_items_end = 0; + int _level_items_end = 0; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx b/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx new file mode 100644 index 00000000..ea20558b --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsFlameGraph.cxx @@ -0,0 +1,814 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsFlameGraph.cxx + * @author rdb + * @date 2022-02-02 + */ + +#include "gtkStatsFlameGraph.h" +#include "gtkStatsLabel.h" +#include "gtkStatsMonitor.h" +#include "pStatCollectorDef.h" + +static const int default_flame_graph_width = 800; +static const int default_flame_graph_height = 150; + +/** + * + */ +GtkStatsFlameGraph:: +GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index, + int collector_index, int frame_number) : + PStatFlameGraph(monitor, thread_index, collector_index, frame_number, 0, 0), + GtkStatsGraph(monitor, false) +{ + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + std::string window_title = get_title_text(); + gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str()); + + // Put some stuff on top of the graph. + _top_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(_graph_vbox), _top_hbox, + FALSE, FALSE, 0); + + _average_check_box = gtk_check_button_new_with_label("Average"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_average_check_box), TRUE); + g_signal_connect(G_OBJECT(_average_check_box), "toggled", + G_CALLBACK(toggled_callback), this); + + // Add a DrawingArea widget on top of the graph, to display all of the scale + // units. + _scale_area = gtk_drawing_area_new(); + g_signal_connect(G_OBJECT(_scale_area), "draw", G_CALLBACK(draw_callback), this); + + _total_label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(_top_hbox), _average_check_box, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(_top_hbox), _scale_area, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(_top_hbox), _total_label, FALSE, FALSE, 0); + + // Listen for mouse scroll and keyboard events. + gtk_widget_add_events(_window, GDK_SCROLL_MASK | GDK_KEY_PRESS_MASK); + g_signal_connect(G_OBJECT(_window), "scroll_event", + G_CALLBACK(scroll_callback), this); + g_signal_connect(G_OBJECT(_window), "key_press_event", + G_CALLBACK(key_press_callback), this); + + gtk_widget_set_size_request(_graph_window, + default_flame_graph_width * monitor->get_resolution() / 96, + default_flame_graph_height * monitor->get_resolution() / 96); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + + // Allow the window to be resized as small as the user likes. We have to do + // this after the window has been shown; otherwise, it will affect the + // window's initial size. + gtk_widget_set_size_request(_window, 0, 0); + + clear_region(); + + if (get_average_mode()) { + start_animation(); + } +} + +/** + * + */ +GtkStatsFlameGraph:: +~GtkStatsFlameGraph() { +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void GtkStatsFlameGraph:: +new_collector(int collector_index) { + GtkStatsGraph::new_collector(collector_index); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void GtkStatsFlameGraph:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str()); + } + } + + if (!_pause) { + update(); + + std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name()); + if (_net_value_text != text) { + _net_value_text = text; + gtk_label_set_text(GTK_LABEL(_total_label), _net_value_text.c_str()); + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void GtkStatsFlameGraph:: +force_redraw() { + if (_cr) { + PStatFlameGraph::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void GtkStatsFlameGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatFlameGraph::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsFlameGraph:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + gtk_widget_queue_draw(_scale_area); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void GtkStatsFlameGraph:: +on_click_label(int collector_index) { + push_collector_index(collector_index); +} + +/** + * Called when the user hovers the mouse over a label. + */ +void GtkStatsFlameGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void GtkStatsFlameGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void GtkStatsFlameGraph:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + int num_bars = get_xsize() / (_pixel_scale * 25); + + _guide_bars.clear(); + + double dist = get_horizontal_scale() / num_bars; + + for (int i = 1; i < num_bars; ++i) { + _guide_bars.push_back(make_guide_bar(i * dist)); + } + + _guide_bars_changed = true; + + nassertv_always(_scale_area != nullptr); + gtk_widget_queue_draw(_scale_area); +} + +/** + * Erases the chart area. + */ +void GtkStatsFlameGraph:: +clear_region() { + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_paint(_cr); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void GtkStatsFlameGraph:: +begin_draw() { + clear_region(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_cr, get_guide_bar(i)); + } +} + +/** + * Should be overridden by the user class. Should draw a single bar at the + * indicated location. + */ +void GtkStatsFlameGraph:: +draw_bar(int depth, int from_x, int to_x, int collector_index, int parent_index) { + double bottom = get_ysize() - depth * _pixel_scale * 5; + double top = bottom - _pixel_scale * 5; + + bool is_highlighted = collector_index == _highlighted_index; + cairo_set_source(_cr, get_collector_pattern(collector_index, is_highlighted)); + + if (to_x < from_x + 3) { + // It's just a tiny sliver. This is a more reliable way to draw it. + cairo_rectangle(_cr, from_x, top, to_x - from_x, bottom - top); + cairo_fill(_cr); + } + else { + double radius = std::min((double)_pixel_scale, (to_x - from_x) / 2.0); + cairo_new_sub_path(_cr); + cairo_arc(_cr, to_x - radius, top + radius, radius, -0.5 * M_PI, 0.0); + cairo_arc(_cr, to_x - radius, bottom - radius, radius, 0.0, 0.5 * M_PI); + cairo_arc(_cr, from_x + radius, bottom - radius, radius, 0.5 * M_PI, M_PI); + cairo_arc(_cr, from_x + radius, top + radius, radius, M_PI, 1.5 * M_PI); + cairo_close_path(_cr); + cairo_fill(_cr); + + if ((to_x - from_x) >= _pixel_scale * 4) { + // Only bother drawing the text if we've got some space to draw on. + int left = std::max(from_x, 0) + _pixel_scale / 2; + int right = std::min(to_x, get_xsize()) - _pixel_scale / 2; + + const PStatClientData *client_data = GtkStatsGraph::_monitor->get_client_data(); + const PStatCollectorDef &def = client_data->get_collector_def(collector_index); + + // Choose a suitable foreground color. + LRGBColor fg = get_collector_text_color(collector_index, is_highlighted); + cairo_set_source_rgb(_cr, fg[0], fg[1], fg[2]); + + PangoLayout *layout = gtk_widget_create_pango_layout(_graph_window, def._name.c_str()); + pango_layout_set_attributes(layout, _pango_attrs); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_width(layout, (right - left) * PANGO_SCALE); + pango_layout_set_height(layout, -1); + + if (!pango_layout_is_ellipsized(layout)) { + // We have room for more. Show the collector's actual parent, if it's + // different than the block it's shown above. + if (def._parent_index > 0 && def._parent_index != parent_index) { + const PStatCollectorDef &parent_def = client_data->get_collector_def(def._parent_index); + std::string long_name = parent_def._name + ":" + def._name; + pango_layout_set_text(layout, long_name.c_str(), long_name.size()); + if (pango_layout_is_ellipsized(layout)) { + // Nope, it's too long, go back. + pango_layout_set_text(layout, def._name.c_str(), def._name.size()); + } + } + else if (collector_index == 0 && get_frame_number() >= 0) { + char text[32]; + sprintf(text, "Frame %d", get_frame_number()); + pango_layout_set_text(layout, text, -1); + if (pango_layout_is_ellipsized(layout)) { + // Nope, it's too long, go back. + pango_layout_set_text(layout, def._name.c_str(), def._name.size()); + } + } + } + + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + // Center the text vertically in the bar. + cairo_move_to(_cr, left, top + (bottom - top - height) / 2); + pango_cairo_show_layout(_cr, layout); + + g_object_unref(layout); + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void GtkStatsFlameGraph:: +end_draw() { + gtk_widget_queue_draw(_graph_window); +} + +/** + * Called at the end of the draw cycle. + */ +void GtkStatsFlameGraph:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool GtkStatsFlameGraph:: +animate(double time, double dt) { + return PStatFlameGraph::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool GtkStatsFlameGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsFlameGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * This is called during the servicing of the draw event; it gives a derived + * class opportunity to do some further painting into the graph window. + */ +void GtkStatsFlameGraph:: +additional_graph_window_paint(cairo_t *cr) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(cr, get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsFlameGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return get_bar_tooltip(pixel_to_depth(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +GtkStatsGraph::DragMode GtkStatsFlameGraph:: +consider_drag_start(int graph_x, int graph_y) { + if (graph_y >= 0 && graph_y < get_ysize()) { + if (graph_x >= 0 && graph_x < get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = graph_x; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + return DM_none; +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +gboolean GtkStatsFlameGraph:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + int depth = pixel_to_depth(graph_y); + int collector_index = get_bar_collector(depth, graph_x); + if (button == 3) { + if (collector_index >= 0) { + GtkWidget *menu = gtk_menu_new(); + _popup_index = collector_index; + + std::string label = get_bar_tooltip(depth, graph_x); + if (!label.empty()) { + GtkWidget *menu_item = gtk_menu_item_new_with_label(label.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + gtk_widget_set_sensitive(menu_item, FALSE); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Set as Focus"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + if (collector_index == 0 && get_collector_index() == 0) { + gtk_widget_set_sensitive(menu_item, FALSE); + } else { + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(+[] (GtkWidget *widget, gpointer data) { + GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data; + self->push_collector_index(self->_popup_index); + }), + this); + } + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_strip_chart, get_thread_index(), collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Strip Chart"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_flame_graph, get_thread_index(), collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Flame Graph"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_choose_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_reset_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); + return TRUE; + } + return FALSE; + } + else if (double_click && button == 1) { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + if (collector_index >= 0) { + push_collector_index(collector_index); + } else { + // Double-clicking the background goes to the top. + clear_history(); + set_collector_index(-1); + } + return TRUE; + } + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_x); + // SetCapture(_graph_window); + return TRUE; + + } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_x = graph_x; + // SetCapture(_graph_window); + return TRUE; + } + + return GtkStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +gboolean GtkStatsFlameGraph:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return GtkStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +gboolean GtkStatsFlameGraph:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int depth = pixel_to_depth(graph_y); + int collector_index = get_bar_collector(depth, graph_x); + on_enter_label(collector_index); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_x >= 0 && graph_x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x)); + return TRUE; + } + } + else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + return TRUE; + } + + return GtkStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsFlameGraph:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + return TRUE; +} + +/** + * Converts a pixel to a depth index. + */ +int GtkStatsFlameGraph:: +pixel_to_depth(int y) const { + return (get_ysize() - 1 - y) / (_pixel_scale * 5); +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void GtkStatsFlameGraph:: +draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to the top. + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + cairo_move_to(cr, x, 0); + cairo_line_to(cr, x, get_ysize()); + cairo_stroke(cr); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void GtkStatsFlameGraph:: +draw_guide_labels(cairo_t *cr) { + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + draw_guide_label(cr, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + draw_guide_label(cr, get_user_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void GtkStatsFlameGraph:: +draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar) { + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, label.c_str()); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - width * _cr_scale); + double to_height = pixel_to_height(x + width * _cr_scale); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + g_object_unref(layout); + return; + } + } + + if (x >= 0 && x < get_xsize()) { + // Now convert our x to a coordinate within our drawing area. + int junk_y; + + x /= _cr_scale; + + // The x coordinate comes from the graph_window. + gtk_widget_translate_coordinates(_graph_window, _scale_area, + x, 0, + &x, &junk_y); + + GtkAllocation allocation; + gtk_widget_get_allocation(_scale_area, &allocation); + + int this_x = x - width / 2; + if (this_x >= 0 && this_x + width < allocation.width) { + cairo_move_to(cr, this_x, allocation.height - height); + pango_cairo_show_layout(cr, layout); + } + } + + g_object_unref(layout); +} + +/** + * Called when the average check box is toggled. + */ +void GtkStatsFlameGraph:: +toggled_callback(GtkToggleButton *button, gpointer data) { + GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data; + + bool active = gtk_toggle_button_get_active(button); + self->set_average_mode(active); + if (active) { + self->start_animation(); + } +} + +/** + * Draws in the scale labels. + */ +gboolean GtkStatsFlameGraph:: +draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data; + self->draw_guide_labels(cr); + + return TRUE; +} + +/** + * + */ +gboolean GtkStatsFlameGraph:: +scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer data) { + GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data; + bool changed = false; + switch (event->direction) { + case GDK_SCROLL_LEFT: + changed = self->prev_frame(); + break; + + case GDK_SCROLL_RIGHT: + changed = self->next_frame(); + break; + } + + if (changed) { + std::string window_title = self->get_title_text(); + gtk_window_set_title(GTK_WINDOW(self->_window), window_title.c_str()); + return TRUE; + } else { + return FALSE; + } +} + +/** + * + */ +gboolean GtkStatsFlameGraph:: +key_press_callback(GtkWidget *widget, GdkEventKey *event, gpointer data) { + GtkStatsFlameGraph *self = (GtkStatsFlameGraph *)data; + bool changed = false; + switch (event->keyval) { + case GDK_KEY_Left: + if (event->state & GDK_MOD1_MASK) { + changed = self->pop_collector_index(); + } else { + changed = self->prev_frame(); + } + break; + + case GDK_KEY_Right: + if ((event->state & GDK_MOD1_MASK) == 0) { + changed = self->next_frame(); + } + break; + + case GDK_KEY_Home: + if ((event->state & GDK_MOD1_MASK) == 0) { + changed = self->first_frame(); + } + break; + + case GDK_KEY_End: + if ((event->state & GDK_MOD1_MASK) == 0) { + changed = self->last_frame(); + } + break; + } + + if (changed) { + std::string window_title = self->get_title_text(); + gtk_window_set_title(GTK_WINDOW(self->_window), window_title.c_str()); + return TRUE; + } else { + return FALSE; + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsFlameGraph.h b/pandatool/src/gtk-stats/gtkStatsFlameGraph.h new file mode 100644 index 00000000..1381ca80 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsFlameGraph.h @@ -0,0 +1,92 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsFlameGraph.h + * @author rdb + * @date 2022-02-02 + */ + +#ifndef GTKSTATSFLAMEGRAPH_H +#define GTKSTATSFLAMEGRAPH_H + +#include "pandatoolbase.h" + +#include "gtkStatsGraph.h" +#include "pStatFlameGraph.h" + +class GtkStatsLabel; + +/** + * A window that draws a flame chart, which shows the collectors explicitly + * stopping and starting, one frame at a time. + */ +class GtkStatsFlameGraph final : public PStatFlameGraph, public GtkStatsGraph { +public: + GtkStatsFlameGraph(GtkStatsMonitor *monitor, int thread_index, + int collector_index=-1, int frame_number=-1); + virtual ~GtkStatsFlameGraph(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + +protected: + virtual void normal_guide_bars(); + + void clear_region(); + virtual void begin_draw(); + virtual void draw_bar(int depth, int from_x, int to_x, + int collector_index, int parent_index); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual void additional_graph_window_paint(cairo_t *cr); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual gboolean handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual gboolean handle_button_release(int graph_x, int graph_y); + virtual gboolean handle_motion(int graph_x, int graph_y); + virtual gboolean handle_leave(); + +private: + int pixel_to_depth(int y) const; + void draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar); + void draw_guide_labels(cairo_t *cr); + void draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar); + + static void toggled_callback(GtkToggleButton *button, gpointer data); + static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data); + static gboolean scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer data); + static gboolean key_press_callback(GtkWidget *widget, GdkEventKey *event, gpointer data); + +private: + std::string _net_value_text; + + GtkWidget *_top_hbox; + GtkWidget *_average_check_box; + GtkWidget *_total_label; + + int _popup_index = -1; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsGraph.cxx b/pandatool/src/gtk-stats/gtkStatsGraph.cxx new file mode 100644 index 00000000..794f4cea --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsGraph.cxx @@ -0,0 +1,702 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsGraph.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsGraph.h" +#include "gtkStatsMonitor.h" +#include "gtkStatsLabelStack.h" +#include "convert_srgb.h" + +const double GtkStatsGraph::rgb_light_gray[3] = { + 0x9a / (double)0xff, 0x9a / (double)0xff, 0x9a / (double)0xff, +}; +const double GtkStatsGraph::rgb_dark_gray[3] = { + 0x33 / (double)0xff, 0x33 / (double)0xff, 0x33 / (double)0xff, +}; +const double GtkStatsGraph::rgb_user_guide_bar[3] = { + 0x82 / (double)0xff, 0x96 / (double)0xff, 0xff / (double)0xff, +}; + +/** + * + */ +GtkStatsGraph:: +GtkStatsGraph(GtkStatsMonitor *monitor, bool has_label_stack) : + _monitor(monitor) +{ + GtkWidget *parent_window = monitor->get_window(); + + GdkWindow *window = gtk_widget_get_window(parent_window); + GdkDisplay *display = gdk_window_get_display(window); + _hand_cursor = gdk_cursor_new_for_display(display, GDK_HAND2); + + int scale = gdk_window_get_scale_factor(window); + _pixel_scale = scale * monitor->get_resolution() * 4 / 96; + + _cr_surface = nullptr; + _cr = nullptr; + _cr_scale = scale; + _pango_attrs = nullptr; + + _surface_xsize = 0; + _surface_ysize = 0; + + _window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint(GTK_WINDOW(_window), GDK_WINDOW_TYPE_HINT_UTILITY); + //gtk_window_set_transient_for(GTK_WINDOW(_window), GTK_WINDOW(parent_window)); + //gtk_window_set_position(GTK_WINDOW(_window), GTK_WIN_POS_CENTER_ON_PARENT); + + gtk_window_add_accel_group(GTK_WINDOW(_window), monitor->get_accel_group()); + + gtk_widget_add_events(_window, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK); + g_signal_connect(G_OBJECT(_window), "delete_event", + G_CALLBACK(window_delete_event), this); + g_signal_connect(G_OBJECT(_window), "destroy", + G_CALLBACK(window_destroy), this); + //g_signal_connect(G_OBJECT(_window), "button_press_event", + // G_CALLBACK(button_press_event_callback), this); + //g_signal_connect(G_OBJECT(_window), "button_release_event", + // G_CALLBACK(button_release_event_callback), this); + //g_signal_connect(G_OBJECT(_window), "motion_notify_event", + // G_CALLBACK(motion_notify_event_callback), this); + + _graph_window = gtk_drawing_area_new(); + gtk_widget_add_events(_graph_window, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); + g_signal_connect(G_OBJECT(_graph_window), "draw", + G_CALLBACK(graph_draw_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "configure_event", + G_CALLBACK(configure_graph_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "button_press_event", + G_CALLBACK(button_press_event_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "button_release_event", + G_CALLBACK(button_release_event_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "motion_notify_event", + G_CALLBACK(motion_notify_event_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "leave_notify_event", + G_CALLBACK(leave_notify_event_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "query-tooltip", + G_CALLBACK(query_tooltip_callback), this); + + gtk_widget_set_has_tooltip(_graph_window, TRUE); + + // A Frame to hold the graph. + _graph_frame = gtk_frame_new(nullptr); + gtk_frame_set_shadow_type(GTK_FRAME(_graph_frame), GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(_graph_frame), _graph_window); + + // A VBox to hold the graph's frame, and any numbers (scale legend? total?) + // above it. + _graph_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_box_pack_end(GTK_BOX(_graph_vbox), _graph_frame, TRUE, TRUE, 0); + + // An HBox to hold the graph's frame, and the scale legend to the right of + // it. + _graph_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(_graph_hbox), _graph_vbox, TRUE, TRUE, 0); + + // An HPaned to hold the label stack and the graph hbox. + if (has_label_stack) { + _hpaned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_wide_handle(GTK_PANED(_hpaned), TRUE); + gtk_container_add(GTK_CONTAINER(_window), _hpaned); + gtk_container_set_border_width(GTK_CONTAINER(_window), 8); + + gtk_paned_pack1(GTK_PANED(_hpaned), _label_stack.get_widget(), FALSE, FALSE); + gtk_paned_pack2(GTK_PANED(_hpaned), _graph_hbox, TRUE, TRUE); + } + else { + gtk_container_add(GTK_CONTAINER(_window), _graph_hbox); + } + + _drag_mode = DM_none; + _potential_drag_mode = DM_none; + _drag_scale_start = 0.0f; + + _pause = false; +} + +/** + * + */ +GtkStatsGraph:: +~GtkStatsGraph() { + if (_timer_id != 0) { + gtk_widget_remove_tick_callback(_graph_window, _timer_id); + _timer_id = 0; + } + + _monitor = nullptr; + release_surface(); + + for (auto &item : _brushes) { + cairo_pattern_destroy(item.second.first); + cairo_pattern_destroy(item.second.second); + } + _brushes.clear(); + _text_colors.clear(); + + _label_stack.clear_labels(); + + if (_pango_attrs != nullptr) { + pango_attr_list_unref(_pango_attrs); + _pango_attrs = nullptr; + } + + if (_window != nullptr) { + GtkWidget *window = _window; + _window = nullptr; + gtk_widget_destroy(window); + } +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void GtkStatsGraph:: +new_collector(int new_collector) { +} + +/** + * Called whenever new data arrives. + */ +void GtkStatsGraph:: +new_data(int thread_index, int frame_number) { +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void GtkStatsGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsGraph:: +set_time_units(int unit_mask) { +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void GtkStatsGraph:: +set_scroll_speed(double scroll_speed) { +} + +/** + * Changes the pause flag for the graph. When this flag is true, the graph + * does not update in response to new data. + */ +void GtkStatsGraph:: +set_pause(bool pause) { + _pause = pause; +} + +/** + * Called when the user guide bars have been changed. + */ +void GtkStatsGraph:: +user_guide_bars_changed() { + if (_scale_area != nullptr) { + gtk_widget_queue_draw(_scale_area); + } + gtk_widget_queue_draw(_graph_window); +} + +/** + * Called when the user single-clicks on a label. + */ +void GtkStatsGraph:: +on_click_label(int collector_index) { +} + +/** + * Called when a pop-up menu should be shown for the label. + */ +void GtkStatsGraph:: +on_popup_label(int collector_index) { +} + +/** + * Called when the user hovers the mouse over a label. + */ +void GtkStatsGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + force_redraw(); + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void GtkStatsGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + force_redraw(); + } +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsGraph:: +get_label_tooltip(int collector_index) const { + return std::string(); +} + +/** + * Should be called when the user closes the associated window. This tells + * the monitor to remove the graph. + */ +void GtkStatsGraph:: +close() { + _label_stack.clear_labels(false); + if (_window != nullptr) { + _window = nullptr; + + GtkStatsMonitor *monitor = _monitor; + _monitor = nullptr; + if (monitor != nullptr) { + monitor->remove_graph(this); + } + } +} + +/** + * Turns on the animation timer, if it hasn't already been turned on. + */ +void GtkStatsGraph:: +start_animation() { + if (_timer_id != 0) { + return; + } + + _time = 0; + _timer_id = gtk_widget_add_tick_callback(_graph_window, tick_callback, + this, nullptr); +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool GtkStatsGraph:: +animate(double time, double dt) { + return false; +} + +/** + * Returns the current window dimensions. + */ +void GtkStatsGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkWindow *window = GTK_WINDOW(_window); + gtk_window_get_position(window, &x, &y); + gtk_window_get_size(window, &width, &height); + maximized = gtk_window_is_maximized(window); + minimized = false; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkWindow *window = GTK_WINDOW(_window); + gtk_window_move(window, x, y); + gtk_window_resize(window, width, height); + if (maximized) { + gtk_window_maximize(window); + } + if (minimized) { + gtk_window_iconify(window); + } +} + +/** + * Returns a pattern suitable for drawing in the indicated collector's color. + */ +cairo_pattern_t *GtkStatsGraph:: +get_collector_pattern(int collector_index, bool highlight) { + Brushes::iterator bi; + bi = _brushes.find(collector_index); + if (bi != _brushes.end()) { + return highlight ? (*bi).second.second : (*bi).second.first; + } + + // Ask the monitor what color this guy should be. + LRGBColor rgb = _monitor->get_collector_color(collector_index); + cairo_pattern_t *pattern = cairo_pattern_create_rgb( + encode_sRGB_float((float)rgb[0]), + encode_sRGB_float((float)rgb[1]), + encode_sRGB_float((float)rgb[2])); + cairo_pattern_t *hpattern = cairo_pattern_create_rgb( + encode_sRGB_float((float)rgb[0] * 0.75f), + encode_sRGB_float((float)rgb[1] * 0.75f), + encode_sRGB_float((float)rgb[2] * 0.75f)); + + _brushes[collector_index] = std::make_pair(pattern, hpattern); + return highlight ? hpattern : pattern; +} + +/** + * Returns a text color suitable for the given collector. + */ +LRGBColor GtkStatsGraph:: +get_collector_text_color(int collector_index, bool highlight) { + TextColors::iterator tci; + tci = _text_colors.find(collector_index); + if (tci != _text_colors.end()) { + return highlight ? (*tci).second.second : (*tci).second.first; + } + + LRGBColor rgb = _monitor->get_collector_color(collector_index); + double bright = + rgb[0] * 0.2126 + + rgb[1] * 0.7152 + + rgb[2] * 0.0722; + LRGBColor color = bright >= 0.5 ? LRGBColor(0) : LRGBColor(1); + LRGBColor hcolor = bright * 0.75 >= 0.5 ? LRGBColor(0) : LRGBColor(1); + + _text_colors[collector_index] = std::make_pair(color, hcolor); + return highlight ? hcolor : color; +} + +/** + * Called when the given collector has changed colors. + */ +void GtkStatsGraph:: +reset_collector_color(int collector_index) { + _brushes.erase(collector_index); + _text_colors.erase(collector_index); + force_redraw(); + _label_stack.update_label_color(collector_index); +} + +/** + * This is called during the servicing of the draw event; it gives a derived + * class opportunity to do some further painting into the graph window. + */ +void GtkStatsGraph:: +additional_graph_window_paint(cairo_t *cr) { +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +GtkStatsGraph::DragMode GtkStatsGraph:: +consider_drag_start(int graph_x, int graph_y) { + return DM_none; +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void GtkStatsGraph:: +set_drag_mode(GtkStatsGraph::DragMode drag_mode) { + _drag_mode = drag_mode; +} + +/** + * Called when the mouse button is depressed within the window, or any nested + * window. + */ +gboolean GtkStatsGraph:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (_potential_drag_mode != DM_none && button == 1) { + set_drag_mode(_potential_drag_mode); + _drag_start_x = graph_x; + _drag_start_y = graph_y; + // SetCapture(_window); + } + return TRUE; +} + +/** + * Called when the mouse button is released within the window, or any nested + * window. + */ +gboolean GtkStatsGraph:: +handle_button_release(int graph_x, int graph_y) { + set_drag_mode(DM_none); + // ReleaseCapture(); + + return handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the window, or any nested window. + */ +gboolean GtkStatsGraph:: +handle_motion(int graph_x, int graph_y) { + _potential_drag_mode = consider_drag_start(graph_x, graph_y); + + GdkWindow *window = gtk_widget_get_window(_window); + + if (_potential_drag_mode == DM_guide_bar || + _drag_mode == DM_guide_bar) { + gdk_window_set_cursor(window, _hand_cursor); + } + else { + gdk_window_set_cursor(window, nullptr); + } + + return TRUE; +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsGraph:: +handle_leave() { + return FALSE; +} + +/** + * Sets up a backing-store bitmap of the indicated size. + */ +void GtkStatsGraph:: +setup_surface(int xsize, int ysize, int scale) { + release_surface(); + + _surface_xsize = xsize; + _surface_ysize = ysize; + _pixel_scale = scale * _monitor->get_resolution() * 4 / 96; + + GdkWindow *window = gtk_widget_get_window(_graph_window); + _cr_surface = gdk_window_create_similar_image_surface(window, CAIRO_FORMAT_RGB24, _surface_xsize, _surface_ysize, 1); + _cr = cairo_create(_cr_surface); + _cr_scale = scale; + + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_paint(_cr); + + // Cache the font scale attribute. + _pango_attrs = pango_attr_list_new(); + PangoAttribute *attr = pango_attr_scale_new(scale * 0.9); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert(_pango_attrs, attr); +} + +/** + * Frees the backing-store bitmap created by setup_surface(). + */ +void GtkStatsGraph:: +release_surface() { + if (_cr_surface != nullptr) { + cairo_surface_destroy(_cr_surface); + cairo_destroy(_cr); + _cr_surface = nullptr; + _cr = nullptr; + } + + if (_pango_attrs != nullptr) { + pango_attr_list_unref(_pango_attrs); + _pango_attrs = nullptr; + } +} + +/** + * Callback when the window is closed by the user. + */ +gboolean GtkStatsGraph:: +window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { + // Returning FALSE to indicate we should destroy the window when the user + // selects "close". + return FALSE; +} + +/** + * Callback when the window is destroyed by the system (or by delete_event). + */ +void GtkStatsGraph:: +window_destroy(GtkWidget *widget, gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + self->close(); +} + +/** + * Fills in the graph window. + */ +gboolean GtkStatsGraph:: +graph_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + + if (self->_cr_surface != nullptr) { + double scale = 1.0 / self->_cr_scale; + cairo_scale(cr, scale, scale); + cairo_set_source_surface(cr, self->_cr_surface, 0, 0); + cairo_paint(cr); + } + + self->additional_graph_window_paint(cr); + + return TRUE; +} + +/** + * Changes the size of the graph window + */ +gboolean GtkStatsGraph:: +configure_graph_callback(GtkWidget *widget, GdkEventConfigure *event, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + + GdkWindow *window = gtk_widget_get_window(widget); + int scale = gdk_window_get_scale_factor(window); + int scaled_xsize = std::max(event->width * scale, 0); + int scaled_ysize = std::max(event->height * scale, 0); + + if (self->_cr == nullptr || + self->_cr_scale != scale || + self->_surface_xsize != scaled_xsize || + self->_surface_ysize != scaled_ysize) { + self->setup_surface(scaled_xsize, scaled_ysize, scale); + self->changed_graph_size(scaled_xsize, scaled_ysize); + } + + return TRUE; +} + +/** + * Called when the mouse button is depressed within the graph window or main + * window. + */ +gboolean GtkStatsGraph:: +button_press_event_callback(GtkWidget *widget, GdkEventButton *event, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + int graph_x, graph_y; + gtk_widget_translate_coordinates(widget, self->_graph_window, + (int)event->x, (int)event->y, + &graph_x, &graph_y); + graph_x *= self->_cr_scale; + graph_y *= self->_cr_scale; + + bool double_click = (event->type == GDK_2BUTTON_PRESS); + + return self->handle_button_press(graph_x, graph_y, + double_click, event->button); +} + +/** + * Called when the mouse button is released within the graph window or main + * window. + */ +gboolean GtkStatsGraph:: +button_release_event_callback(GtkWidget *widget, GdkEventButton *event, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + int graph_x, graph_y; + gtk_widget_translate_coordinates(widget, self->_graph_window, + (int)event->x, (int)event->y, + &graph_x, &graph_y); + graph_x *= self->_cr_scale; + graph_y *= self->_cr_scale; + + return self->handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window or main window. + */ +gboolean GtkStatsGraph:: +motion_notify_event_callback(GtkWidget *widget, GdkEventMotion *event, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + int graph_x, graph_y; + gtk_widget_translate_coordinates(widget, self->_graph_window, + (int)event->x, (int)event->y, + &graph_x, &graph_y); + graph_x *= self->_cr_scale; + graph_y *= self->_cr_scale; + + return self->handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsGraph:: +leave_notify_event_callback(GtkWidget *widget, GdkEventCrossing *event, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + return self->handle_leave(); +} + +/** + * Called when a tooltip should be displayed. + */ +gboolean GtkStatsGraph:: +query_tooltip_callback(GtkWidget *widget, gint x, gint y, + gboolean keyboard_tip, GtkTooltip *tooltip, + gpointer data) { + GtkStatsGraph *self = (GtkStatsGraph *)data; + x *= self->_cr_scale; + y *= self->_cr_scale; + + std::string text = self->get_graph_tooltip(x, y); + gtk_tooltip_set_text(tooltip, text.c_str()); + return !text.empty(); +} + +/** + * Called to update the animations. + */ +gboolean GtkStatsGraph:: +tick_callback(GtkWidget *widget, GdkFrameClock *clock, gpointer data) { + GtkStatsGraph *graph = (GtkStatsGraph *)data; + gint64 new_time = gdk_frame_clock_get_frame_time(clock); + if (graph->_time == 0) { + // First frame, so we don't have a dt yet. + graph->_time = new_time; + return TRUE; + } + gint64 delta = new_time - graph->_time; + if (delta == 0) { + return TRUE; + } + if (graph->animate(new_time / 1000000.0, delta / 1000000.0)) { + graph->_time = new_time; + return TRUE; + } else { + graph->_timer_id = 0; + return FALSE; + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsGraph.h b/pandatool/src/gtk-stats/gtkStatsGraph.h new file mode 100644 index 00000000..bd2f3339 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsGraph.h @@ -0,0 +1,170 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsGraph.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSGRAPH_H +#define GTKSTATSGRAPH_H + +#include "pandatoolbase.h" +#include "gtkStatsLabelStack.h" +#include "pmap.h" +#include "luse.h" + +#include +#include + +class GtkStatsMonitor; + +/** + * This is just an abstract base class to provide a common pointer type for + * the various kinds of graphs that may be created for a GtkStatsMonitor. + */ +class GtkStatsGraph { +public: + // What is the user adjusting by dragging the mouse in a window? + enum DragMode { + DM_none, + DM_scale, + DM_guide_bar, + DM_new_guide_bar, + DM_sizing, + DM_pan, + }; + +public: + GtkStatsGraph(GtkStatsMonitor *monitor, bool has_label_stack); + virtual ~GtkStatsGraph(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw()=0; + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void user_guide_bars_changed(); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + + void reset_collector_color(int collector_index); + +protected: + void close(); + + void start_animation(); + virtual bool animate(double time, double dt); + + void get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + cairo_pattern_t *get_collector_pattern(int collector_index, bool highlight = false); + LRGBColor get_collector_text_color(int collector_index, bool highlight = false); + + virtual void additional_graph_window_paint(cairo_t *cr); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + virtual void set_drag_mode(DragMode drag_mode); + + virtual gboolean handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual gboolean handle_button_release(int graph_x, int graph_y); + virtual gboolean handle_motion(int graph_x, int graph_y); + virtual gboolean handle_leave(); + +protected: + // Table of patterns for our various collectors. + typedef pmap > Brushes; + Brushes _brushes; + + typedef pmap > TextColors; + TextColors _text_colors; + + GtkStatsMonitor *_monitor; + GtkWidget *_parent_window = nullptr; + GtkWidget *_window = nullptr; + GtkWidget *_graph_frame; + GtkWidget *_graph_window = nullptr; + GtkWidget *_graph_hbox; + GtkWidget *_graph_vbox; + GtkWidget *_hpaned; + GtkWidget *_scale_area = nullptr; + GtkStatsLabelStack _label_stack; + + GdkCursor *_hand_cursor; + + cairo_surface_t *_cr_surface; + cairo_t *_cr; + int _surface_xsize, _surface_ysize; + PangoAttrList *_pango_attrs; + int _cr_scale; + int _pixel_scale; + + DragMode _drag_mode; + DragMode _potential_drag_mode; + int _drag_start_x, _drag_start_y; + double _drag_scale_start; + int _drag_guide_bar; + + int _highlighted_index = -1; + + bool _pause; + + guint _timer_id = 0; + gint64 _time = 0; + + static const double rgb_white[3]; + static const double rgb_light_gray[3]; + static const double rgb_dark_gray[3]; + static const double rgb_black[3]; + static const double rgb_user_guide_bar[3]; + +private: + void setup_surface(int xsize, int ysize, int scale); + void release_surface(); + + static gboolean window_delete_event(GtkWidget *widget, GdkEvent *event, + gpointer data); + static void window_destroy(GtkWidget *widget, gpointer data); + static gboolean graph_draw_callback(GtkWidget *widget, + cairo_t *cr, gpointer data); + static gboolean configure_graph_callback(GtkWidget *widget, + GdkEventConfigure *event, + gpointer data); + +protected: + static gboolean button_press_event_callback(GtkWidget *widget, + GdkEventButton *event, + gpointer data); + static gboolean button_release_event_callback(GtkWidget *widget, + GdkEventButton *event, + gpointer data); + static gboolean motion_notify_event_callback(GtkWidget *widget, + GdkEventMotion *event, + gpointer data); + static gboolean leave_notify_event_callback(GtkWidget *widget, + GdkEventCrossing *event, + gpointer data); + static gboolean query_tooltip_callback(GtkWidget *widget, gint x, gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, gpointer data); + static gboolean tick_callback(GtkWidget *widget, GdkFrameClock *clock, + gpointer data); +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.cxx b/pandatool/src/gtk-stats/gtkStatsLabel.cxx new file mode 100644 index 00000000..bb766038 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabel.cxx @@ -0,0 +1,283 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsLabel.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsLabel.h" +#include "gtkStatsMonitor.h" +#include "gtkStatsGraph.h" +#include "convert_srgb.h" + +int GtkStatsLabel::_left_margin = 6; +int GtkStatsLabel::_right_margin = 2; +int GtkStatsLabel::_top_margin = 1; +int GtkStatsLabel::_bottom_margin = 1; + +/** + * + */ +GtkStatsLabel:: +GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname, + bool align_right) : + _monitor(monitor), + _graph(graph), + _thread_index(thread_index), + _collector_index(collector_index), + _align_right(align_right) +{ + _widget = gtk_drawing_area_new(); + gtk_widget_add_events(_widget, + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | + GDK_BUTTON_PRESS_MASK); + g_signal_connect(G_OBJECT(_widget), "draw", + G_CALLBACK(draw_callback), this); + g_signal_connect(G_OBJECT(_widget), "enter_notify_event", + G_CALLBACK(enter_notify_event_callback), this); + g_signal_connect(G_OBJECT(_widget), "leave_notify_event", + G_CALLBACK(leave_notify_event_callback), this); + g_signal_connect(G_OBJECT(_widget), "button_press_event", + G_CALLBACK(button_press_event_callback), this); + g_signal_connect(G_OBJECT(_widget), "query-tooltip", + G_CALLBACK(query_tooltip_callback), this); + + gtk_widget_set_has_tooltip(_widget, TRUE); + + _highlight = false; + _mouse_within = false; + + update_color(); + update_text(use_fullname); + gtk_widget_show_all(_widget); +} + +/** + * + */ +GtkStatsLabel:: +~GtkStatsLabel() { + if (_layout) { + g_object_unref(_layout); + _layout = nullptr; + } +} + +/** + * Returns the widget for this label. + */ +GtkWidget *GtkStatsLabel:: +get_widget() const { + return _widget; +} + +/** + * Returns the height of the label as we requested it. + */ +int GtkStatsLabel:: +get_height() const { + return _height; +} + +/** + * Returns the collector this label represents. + */ +int GtkStatsLabel:: +get_collector_index() const { + return _collector_index; +} + +/** + * Returns the thread index. + */ +int GtkStatsLabel:: +get_thread_index() const { + return _thread_index; +} + +/** + * Enables or disables the visual highlight for this label. + */ +void GtkStatsLabel:: +set_highlight(bool highlight) { + if (_highlight != highlight) { + _highlight = highlight; + gtk_widget_queue_draw(_widget); + } +} + +/** + * Returns true if the visual highlight for this label is enabled. + */ +bool GtkStatsLabel:: +get_highlight() const { + return _highlight; +} + +/** + * Updates the colors. + */ +void GtkStatsLabel:: +update_color() { + // Set the fg and bg colors on the label. + LRGBColor rgb = _monitor->get_collector_color(_collector_index); + _bg_color = LRGBColor( + encode_sRGB_float((float)rgb[0]), + encode_sRGB_float((float)rgb[1]), + encode_sRGB_float((float)rgb[2])); + + _highlight_bg_color = LRGBColor( + encode_sRGB_float((float)rgb[0] * 0.75f), + encode_sRGB_float((float)rgb[1] * 0.75f), + encode_sRGB_float((float)rgb[2] * 0.75f)); + + // Should our foreground be black or white? + PN_stdfloat bright = _bg_color.dot(LRGBColor(0.2126, 0.7152, 0.0722)); + if (bright >= 0.5) { + _fg_color = LRGBColor(0); + } else { + _fg_color = LRGBColor(1); + } + if (bright * 0.75 >= 0.5) { + _highlight_fg_color = LRGBColor(0); + } else { + _highlight_fg_color = LRGBColor(1); + } + + gtk_widget_queue_draw(_widget); +} + +/** + * Set to true if the full name of the collector should be shown. + */ +void GtkStatsLabel:: +update_text(bool use_fullname) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (use_fullname) { + _text = client_data->get_collector_fullname(_collector_index); + } else { + _text = client_data->get_collector_name(_collector_index); + } + + // Make up a PangoLayout to represent the text. + if (_layout) { + g_object_unref(_layout); + } + _layout = gtk_widget_create_pango_layout(_widget, _text.c_str()); + + // What are the extents of the text? This determines the minimum size of + // our widget. + int width, height; + pango_layout_get_pixel_size(_layout, &width, &height); + _ideal_width = width + _left_margin + _right_margin; + _height = height + _top_margin + _bottom_margin; + gtk_widget_set_size_request(_widget, _ideal_width, _height); +} + +/** + * Used internally to indicate whether the mouse is within the label's widget. + */ +void GtkStatsLabel:: +set_mouse_within(bool mouse_within) { + if (_mouse_within != mouse_within) { + _mouse_within = mouse_within; + gtk_widget_queue_draw(_widget); + } +} + +/** + * Draws the background color of the label. + */ +gboolean GtkStatsLabel:: +draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsLabel *self = (GtkStatsLabel *)data; + + LRGBColor bg, fg; + if (self->_highlight || self->_mouse_within) { + bg = self->_highlight_bg_color; + fg = self->_highlight_fg_color; + } else { + bg = self->_bg_color; + fg = self->_fg_color; + + } + cairo_set_source_rgb(cr, bg[0], bg[1], bg[2]); + + GtkAllocation allocation; + gtk_widget_get_allocation(widget, &allocation); + + cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); + cairo_fill(cr); + + int width, height; + pango_layout_get_pixel_size(self->_layout, &width, &height); + + cairo_set_source_rgb(cr, fg[0], fg[1], fg[2]); + if (self->_align_right) { + cairo_move_to(cr, allocation.width - width - _right_margin, _top_margin); + } else { + cairo_move_to(cr, _left_margin, _top_margin); + } + pango_cairo_show_layout(cr, self->_layout); + + return TRUE; +} + +/** + * Called when the mouse enters the label region + */ +gboolean GtkStatsLabel:: +enter_notify_event_callback(GtkWidget *widget, GdkEventCrossing *event, + gpointer data) { + GtkStatsLabel *self = (GtkStatsLabel *)data; + self->set_mouse_within(true); + return TRUE; +} + +/** + * Called when the mouse leaves the label region + */ +gboolean GtkStatsLabel:: +leave_notify_event_callback(GtkWidget *widget, GdkEventCrossing *event, + gpointer data) { + GtkStatsLabel *self = (GtkStatsLabel *)data; + self->set_mouse_within(false); + return TRUE; +} + +/** + * Called when the mouse button is depressed within the label. + */ +gboolean GtkStatsLabel:: +button_press_event_callback(GtkWidget *widget, GdkEventButton *event, + gpointer data) { + GtkStatsLabel *self = (GtkStatsLabel *)data; + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + self->_graph->on_click_label(self->_collector_index); + } + else if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + self->_graph->on_popup_label(self->_collector_index); + } + return TRUE; +} + +/** + * Called when a tooltip should be displayed. + */ +gboolean GtkStatsLabel:: +query_tooltip_callback(GtkWidget *widget, gint x, gint y, + gboolean keyboard_tip, GtkTooltip *tooltip, + gpointer data) { + GtkStatsLabel *self = (GtkStatsLabel *)data; + + std::string text = self->_graph->get_label_tooltip(self->_collector_index); + gtk_tooltip_set_text(tooltip, text.c_str()); + return !text.empty(); +} diff --git a/pandatool/src/gtk-stats/gtkStatsLabel.h b/pandatool/src/gtk-stats/gtkStatsLabel.h new file mode 100644 index 00000000..3f07a4e9 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabel.h @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsLabel.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSLABEL_H +#define GTKSTATSLABEL_H + +#include "pandatoolbase.h" +#include "luse.h" + +#include +#include + +class GtkStatsMonitor; +class GtkStatsGraph; + +/** + * A text label that will draw in color appropriate for a particular + * collector. It also responds when the user double-clicks on it. This is + * handy for putting colored labels on strip charts. + */ +class GtkStatsLabel { +public: + GtkStatsLabel(GtkStatsMonitor *monitor, GtkStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname, + bool align_right = true); + ~GtkStatsLabel(); + + GtkWidget *get_widget() const; + int get_height() const; + + int get_collector_index() const; + int get_thread_index() const; + + void set_highlight(bool highlight); + bool get_highlight() const; + + void update_color(); + void update_text(bool use_fullname); + +private: + void set_mouse_within(bool mouse_within); + static gboolean draw_callback(GtkWidget *widget, + cairo_t *cr, gpointer data); + static gboolean enter_notify_event_callback(GtkWidget *widget, + GdkEventCrossing *event, + gpointer data); + static gboolean leave_notify_event_callback(GtkWidget *widget, + GdkEventCrossing *event, + gpointer data); + static gboolean button_press_event_callback(GtkWidget *widget, + GdkEventButton *event, + gpointer data); + static gboolean query_tooltip_callback(GtkWidget *widget, gint x, gint y, + gboolean keyboard_tip, GtkTooltip *tooltip, + gpointer data); + + GtkStatsMonitor *_monitor; + GtkStatsGraph *_graph; + int _thread_index; + int _collector_index; + std::string _text; + GtkWidget *_widget; + LRGBColor _fg_color; + LRGBColor _highlight_fg_color; + LRGBColor _bg_color; + LRGBColor _highlight_bg_color; + PangoLayout *_layout = nullptr; + + int _height; + int _ideal_width; + bool _highlight; + bool _mouse_within; + bool _align_right; + + static int _left_margin, _right_margin; + static int _top_margin, _bottom_margin; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx b/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx new file mode 100644 index 00000000..cc887081 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabelStack.cxx @@ -0,0 +1,143 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsLabelStack.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsLabelStack.h" +#include "gtkStatsLabel.h" +#include "pnotify.h" + +/** + * + */ +GtkStatsLabelStack:: +GtkStatsLabelStack() { + _widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + _highlight_label = -1; +} + +/** + * + */ +GtkStatsLabelStack:: +~GtkStatsLabelStack() { + clear_labels(); +} + +/** + * Returns the widget for this stack. + */ +GtkWidget *GtkStatsLabelStack:: +get_widget() const { + return _widget; +} + +/** + * Returns the y position of the indicated label's bottom edge, relative to + * the indicated target widget. + */ +int GtkStatsLabelStack:: +get_label_y(int label_index, GtkWidget *target_widget) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + + GtkStatsLabel *label = _labels[label_index]; + + int x, y; + gtk_widget_translate_coordinates(label->get_widget(), target_widget, + 0, 0, &x, &y); + y += label->get_height(); + return y; +} + +/** + * Returns the height of the indicated label. + */ +int GtkStatsLabelStack:: +get_label_height(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + return _labels[label_index]->get_height(); +} + +/** + * Returns the collector index associated with the indicated label. + */ +int GtkStatsLabelStack:: +get_label_collector_index(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), -1); + return _labels[label_index]->get_collector_index(); +} + +/** + * Removes the set of labels and starts a new set. + */ +void GtkStatsLabelStack:: +clear_labels(bool delete_widgets) { + for (GtkStatsLabel *label : _labels) { + if (delete_widgets) { + gtk_container_remove(GTK_CONTAINER(_widget), label->get_widget()); + } + delete label; + } + _labels.clear(); +} + +/** + * Adds a new label to the top of the stack; returns the new label index. + */ +int GtkStatsLabelStack:: +add_label(GtkStatsMonitor *monitor, GtkStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname) { + GtkStatsLabel *label = + new GtkStatsLabel(monitor, graph, thread_index, collector_index, use_fullname); + + gtk_box_pack_end(GTK_BOX(_widget), label->get_widget(), + FALSE, FALSE, 0); + + int label_index = (int)_labels.size(); + _labels.push_back(label); + + return label_index; +} + +/** + * Returns the number of labels in the stack. + */ +int GtkStatsLabelStack:: +get_num_labels() const { + return _labels.size(); +} + +/** + * Draws a highlight around the label representing the indicated collector, + * and removes the highlight from any other label. Specify -1 to remove the + * highlight from all labels. + */ +void GtkStatsLabelStack:: +highlight_label(int collector_index) { + if (_highlight_label != collector_index) { + _highlight_label = collector_index; + for (GtkStatsLabel *label : _labels) { + label->set_highlight(label->get_collector_index() == _highlight_label); + } + } +} + +/** + * Refreshes the color of the label with the given index. + */ +void GtkStatsLabelStack:: +update_label_color(int collector_index) { + for (GtkStatsLabel *label : _labels) { + if (label->get_collector_index() == collector_index) { + label->update_color(); + } + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsLabelStack.h b/pandatool/src/gtk-stats/gtkStatsLabelStack.h new file mode 100644 index 00000000..42247239 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsLabelStack.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsLabelStack.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSLABELSTACK_H +#define GTKSTATSLABELSTACK_H + +#include "pandatoolbase.h" +#include "pvector.h" + +#include + +class GtkStatsLabel; +class GtkStatsMonitor; +class GtkStatsGraph; + +/** + * A widget that contains a stack of labels from bottom to top. + */ +class GtkStatsLabelStack { +public: + GtkStatsLabelStack(); + ~GtkStatsLabelStack(); + + GtkWidget *get_widget() const; + + int get_label_y(int label_index, GtkWidget *target_widget) const; + int get_label_height(int label_index) const; + int get_label_collector_index(int label_index) const; + + void clear_labels(bool delete_widgets = true); + int add_label(GtkStatsMonitor *monitor, GtkStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname); + int get_num_labels() const; + + void highlight_label(int collector_index); + void update_label_color(int collector_index); + +private: + GtkWidget *_widget; + int _highlight_label; + + typedef pvector Labels; + Labels _labels; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.I b/pandatool/src/gtk-stats/gtkStatsMonitor.I new file mode 100644 index 00000000..867c4349 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.I @@ -0,0 +1,47 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsMonitor.I + * @author drose + * @date 2006-01-16 + */ + +/** + * + */ +INLINE GtkStatsMonitor::MenuDef:: +MenuDef(ChartType chart_type, int thread_index, int collector_index, + int frame_number, bool show_level) : + _chart_type(chart_type), + _thread_index(thread_index), + _collector_index(collector_index), + _frame_number(frame_number), + _show_level(show_level), + _monitor(nullptr) +{ +} + +/** + * + */ +INLINE bool GtkStatsMonitor::MenuDef:: +operator < (const MenuDef &other) const { + if (_chart_type != other._chart_type) { + return _chart_type < other._chart_type; + } + if (_thread_index != other._thread_index) { + return _thread_index < other._thread_index; + } + if (_collector_index != other._collector_index) { + return _collector_index < other._collector_index; + } + if (_frame_number != other._frame_number) { + return _frame_number < other._frame_number; + } + return (int)_show_level < (int)other._show_level; +} diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.cxx b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx new file mode 100644 index 00000000..2be16d94 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.cxx @@ -0,0 +1,882 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsMonitor.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsMonitor.h" +#include "gtkStats.h" +#include "gtkStatsServer.h" +#include "gtkStatsStripChart.h" +#include "gtkStatsChartMenu.h" +#include "gtkStatsPianoRoll.h" +#include "gtkStatsFlameGraph.h" +#include "gtkStatsTimeline.h" +#include "pStatGraph.h" +#include "pStatCollectorDef.h" + +#include "convert_srgb.h" + +/** + * + */ +GtkStatsMonitor:: +GtkStatsMonitor(GtkStatsServer *server) : PStatMonitor(server) { + _window = server->get_window(); + _menu_bar = server->get_menu_bar(); + _status_bar = server->get_status_bar(); + + // These will be filled in later when the menu is created. + _scroll_speed = 0.0; + _pause = false; + _next_chart_index = 2; + + _resolution = gdk_screen_get_resolution(gdk_screen_get_default()); + setup_speed_menu(); + setup_frame_rate_label(); +} + +/** + * + */ +GtkStatsMonitor:: +~GtkStatsMonitor() { + close(); +} + +/** + * Closes all the graphs associated with this monitor. + */ +void GtkStatsMonitor:: +close() { + PStatMonitor::close(); + + remove_all_graphs(); + + for (GtkWidget *flow_box_child : _status_bar_labels) { + gtk_container_remove(GTK_CONTAINER(_status_bar), flow_box_child); + g_object_unref(flow_box_child); + } + _status_bar_collectors.clear(); + _status_bar_labels.clear(); + + if (_speed_menu_item != nullptr) { + gtk_container_remove(GTK_CONTAINER(_menu_bar), _speed_menu_item); + _speed_menu_item = nullptr; + } + + for (GtkStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->remove_from_menu_bar(_menu_bar); + delete chart_menu; + } + _chart_menus.clear(); + + if (_frame_rate_menu_item != nullptr) { + gtk_container_remove(GTK_CONTAINER(_menu_bar), _frame_rate_menu_item); + _frame_rate_menu_item = nullptr; + } + + _next_chart_index = 2; +} + +/** + * Should be redefined to return a descriptive name for the type of + * PStatsMonitor this is. + */ +std::string GtkStatsMonitor:: +get_monitor_name() { + return "GtkStats"; +} + +/** + * Called after the monitor has been fully set up. At this time, it will have + * a valid _client_data pointer, and things like is_alive() and close() will + * be meaningful. However, we may not yet know who we're connected to + * (is_client_known() may return false), and we may not know anything about + * the threads or collectors we're about to get data on. + */ +void GtkStatsMonitor:: +initialized() { +} + +/** + * Called when the "hello" message has been received from the client. At this + * time, the client's hostname and program name will be known. + */ +void GtkStatsMonitor:: +got_hello() { +} + +/** + * Like got_hello(), this is called when the "hello" message has been received + * from the client. At this time, the client's hostname and program name will + * be known. However, the client appears to be an incompatible version and + * the connection will be terminated; the monitor should issue a message to + * that effect. + */ +void GtkStatsMonitor:: +got_bad_version(int client_major, int client_minor, + int server_major, int server_minor) { + std::ostringstream str; + str << "Unable to honor connection attempt from " + << get_client_progname() << " on " << get_client_hostname() + << ": unsupported PStats version " + << client_major << "." << client_minor; + + if (server_minor == 0) { + str << " (server understands version " << server_major + << "." << server_minor << " only)."; + } else { + str << " (server understands versions " << server_major + << ".0 through " << server_major << "." << server_minor << ")."; + } + + std::string message = str.str(); + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", message.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +/** + * Called whenever a new Collector definition is received from the client. + * Generally, the client will send all of its collectors over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Collector definitions midstream. + */ +void GtkStatsMonitor:: +new_collector(int collector_index) { + for (GtkStatsGraph *graph : _graphs) { + graph->new_collector(collector_index); + } +} + +/** + * Called whenever a new Thread definition is received from the client. + * Generally, the client will send all of its threads over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Thread definitions midstream. + */ +void GtkStatsMonitor:: +new_thread(int thread_index) { + GtkStatsChartMenu *chart_menu = new GtkStatsChartMenu(this, thread_index); + chart_menu->add_to_menu_bar(_menu_bar, _next_chart_index); + ++_next_chart_index; + _chart_menus.push_back(chart_menu); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void GtkStatsMonitor:: +new_data(int thread_index, int frame_number) { + PStatMonitor::new_data(thread_index, frame_number); + + for (GtkStatsGraph *graph : _graphs) { + graph->new_data(thread_index, frame_number); + } + + if (thread_index == 0) { + update_status_bar(); + } + + if (!_have_data) { + open_default_graphs(); + _have_data = true; + + // Flash the window. + gtk_window_set_urgency_hint(GTK_WINDOW(_window), TRUE); + } +} + +/** + * Called when a thread should be removed from the list of threads. + */ +void GtkStatsMonitor:: +remove_thread(int thread_index) { + for (ChartMenus::iterator it = _chart_menus.begin(); it != _chart_menus.end(); ++it) { + GtkStatsChartMenu *chart_menu = *it; + if (chart_menu->get_thread_index() == thread_index) { + chart_menu->remove_from_menu_bar(_menu_bar); + delete chart_menu; + _chart_menus.erase(it); + --_next_chart_index; + return; + } + } +} + +/** + * Called whenever the connection to the client has been lost. This is a + * permanent state change. The monitor should update its display to represent + * this, and may choose to close down automatically. + */ +void GtkStatsMonitor:: +lost_connection() { + nout << "Lost connection to " << get_client_hostname() << "\n"; +} + +/** + * If has_idle() returns true, this will be called periodically to allow the + * monitor to update its display or whatever it needs to do. + */ +void GtkStatsMonitor:: +idle() { + // Check if any of our chart menus need updating. + for (GtkStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->check_update(); + } + + // Update the frame rate label from the main thread (thread 0). + const PStatThreadData *thread_data = get_client_data()->get_thread_data(0); + double frame_rate = thread_data->get_frame_rate(); + if (frame_rate != 0.0f) { + char buffer[128]; + sprintf(buffer, "%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate); + + gtk_label_set_text(GTK_LABEL(_frame_rate_label), buffer); + + if (!_status_bar_labels.empty()) { + GtkWidget *label = gtk_bin_get_child(GTK_BIN(_status_bar_labels[0])); + gtk_label_set_text(GTK_LABEL(label), buffer); + } + } +} + +/** + * Should be redefined to return true if you want to redefine idle() and + * expect it to be called. + */ +bool GtkStatsMonitor:: +has_idle() { + return true; +} + +/** + * Called when the user guide bars have been changed. + */ +void GtkStatsMonitor:: +user_guide_bars_changed() { + for (GtkStatsGraph *graph : _graphs) { + graph->user_guide_bars_changed(); + } +} + +/** + * Returns the window handle to the monitor's window. + */ +GtkWidget *GtkStatsMonitor:: +get_window() const { + return _window; +} + +/** + * + */ +GtkAccelGroup *GtkStatsMonitor:: +get_accel_group() const { + return ((GtkStatsServer *)_server)->get_accel_group(); +} + +/** + * Returns the screen DPI. + */ +double GtkStatsMonitor:: +get_resolution() const { + return _resolution; +} + +/** + * Opens a new timeline. + */ +PStatGraph *GtkStatsMonitor:: +open_timeline() { + GtkStatsTimeline *graph = new GtkStatsTimeline(this); + add_graph(graph); + return graph; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *GtkStatsMonitor:: +open_flame_graph(int thread_index, int collector_index, int frame_number) { + GtkStatsFlameGraph *graph = new GtkStatsFlameGraph(this, thread_index, collector_index, frame_number); + add_graph(graph); + return graph; +} + +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *GtkStatsMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + GtkStatsStripChart *graph = + new GtkStatsStripChart(this, thread_index, collector_index, show_level); + add_graph(graph); + return graph; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *GtkStatsMonitor:: +open_piano_roll(int thread_index) { + GtkStatsPianoRoll *graph = new GtkStatsPianoRoll(this, thread_index); + add_graph(graph); + return graph; +} + +/** + * Opens a dialog to change the given collector color. + */ +void GtkStatsMonitor:: +choose_collector_color(int collector_index) { + const LRGBColor ¤t = get_collector_color(collector_index); + + GtkWidget *chooser = gtk_color_chooser_dialog_new(nullptr, GTK_WINDOW(_window)); + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(chooser), FALSE); + + GdkRGBA rgba; + rgba.red = encode_sRGB_float((float)current[0]); + rgba.green = encode_sRGB_float((float)current[1]); + rgba.blue = encode_sRGB_float((float)current[2]); + rgba.alpha = 1.0; + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(chooser), &rgba); + + if (gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_OK) { + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(chooser), &rgba); + LRGBColor result( + decode_sRGB_float((float)rgba.red), + decode_sRGB_float((float)rgba.green), + decode_sRGB_float((float)rgba.blue)); + + set_collector_color(collector_index, result); + + for (GtkStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } + + gtk_widget_destroy(chooser); +} + +/** + * Resets the color of the given collector to the default. + */ +void GtkStatsMonitor:: +reset_collector_color(int collector_index) { + clear_collector_color(collector_index); + + for (GtkStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } +} + +/** + * Adds a new MenuDef to the monitor, or returns an existing one if there is + * already one just like it. + */ +const GtkStatsMonitor::MenuDef *GtkStatsMonitor:: +add_menu(const MenuDef &menu_def) { + std::pair result = _menus.insert(menu_def); + Menus::iterator mi = result.first; + const GtkStatsMonitor::MenuDef &new_menu_def = (*mi); + if (result.second) { + // A new MenuDef was inserted. + ((GtkStatsMonitor::MenuDef &)new_menu_def)._monitor = this; + } + return &new_menu_def; +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsMonitor:: +set_time_units(int unit_mask) { + for (GtkStatsGraph *graph : _graphs) { + graph->set_time_units(unit_mask); + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speeds for all graphs to the indicated value. + */ +void GtkStatsMonitor:: +set_scroll_speed(double scroll_speed) { + _scroll_speed = scroll_speed; + + // First, change all of the open graphs appropriately. + for (GtkStatsGraph *graph : _graphs) { + graph->set_scroll_speed(_scroll_speed); + } +} + +/** + * Called when the user selects a pause on or pause off option from the menu. + */ +void GtkStatsMonitor:: +set_pause(bool pause) { + _pause = pause; + + // First, change all of the open graphs appropriately. + for (GtkStatsGraph *graph : _graphs) { + graph->set_pause(_pause); + } +} + +/** + * Adds the newly-created graph to the list of managed graphs. + */ +void GtkStatsMonitor:: +add_graph(GtkStatsGraph *graph) { + _graphs.insert(graph); + + graph->set_time_units(((GtkStatsServer *)_server)->get_time_units()); + graph->set_scroll_speed(_scroll_speed); + graph->set_pause(_pause); +} + +/** + * Deletes the indicated graph. + */ +void GtkStatsMonitor:: +remove_graph(GtkStatsGraph *graph) { + Graphs::iterator gi = _graphs.find(graph); + if (gi != _graphs.end()) { + _graphs.erase(gi); + delete graph; + } +} + +/** + * Deletes all open graphs. + */ +void GtkStatsMonitor:: +remove_all_graphs() { + for (GtkStatsGraph *graph : _graphs) { + delete graph; + } + _graphs.clear(); +} + +/** + * Creates the "Speed" pulldown menu. + */ +void GtkStatsMonitor:: +setup_speed_menu() { + GtkWidget *menu = gtk_menu_new(); + + _speed_menu_item = gtk_menu_item_new_with_label("Speed"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(_speed_menu_item), menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), _speed_menu_item); + + GSList *group = nullptr; + GtkWidget *item; + item = gtk_radio_menu_item_new_with_label(group, "1"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_scroll_speed(1); + } + }), this); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + + item = gtk_radio_menu_item_new_with_label(group, "2"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_scroll_speed(2); + } + }), this); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + + item = gtk_radio_menu_item_new_with_label(group, "3"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_scroll_speed(3); + } + }), this); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + + item = gtk_radio_menu_item_new_with_label(group, "6"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_scroll_speed(6); + } + }), this); + group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + + item = gtk_radio_menu_item_new_with_label(group, "12"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_scroll_speed(12); + } + }), this); + //group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)); + + item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + item = gtk_check_menu_item_new_with_label("pause"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(G_OBJECT(item), "toggled", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsMonitor *self = (GtkStatsMonitor *)data; + self->set_pause(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))); + }), this); + + set_scroll_speed(3); + set_pause(false); + + gtk_widget_show_all(_speed_menu_item); + ++_next_chart_index; +} + +/** + * Creates the frame rate label on the right end of the menu bar. This is + * used as a text label to display the main thread's frame rate to the user, + * although it is implemented as a right-justified toplevel menu item that + * doesn't open to anything. + */ +void GtkStatsMonitor:: +setup_frame_rate_label() { + _frame_rate_menu_item = gtk_menu_item_new(); + _frame_rate_label = gtk_label_new(""); + gtk_container_add(GTK_CONTAINER(_frame_rate_menu_item), _frame_rate_label); + gtk_widget_set_sensitive(_frame_rate_menu_item, FALSE); + + gtk_widget_show(_frame_rate_menu_item); + gtk_widget_show(_frame_rate_label); + gtk_menu_item_set_right_justified(GTK_MENU_ITEM(_frame_rate_menu_item), TRUE); + + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), _frame_rate_menu_item); +} + +/** + * Updates the status bar. + */ +void GtkStatsMonitor:: +update_status_bar() { + const PStatClientData *client_data = get_client_data(); + if (client_data == nullptr) { + return; + } + + const PStatThreadData *thread_data = get_client_data()->get_thread_data(0); + if (thread_data == nullptr || thread_data->is_empty()) { + return; + } + int frame_number = thread_data->get_latest_frame_number(); + const PStatFrameData &frame_data = thread_data->get_latest_frame(); + + pvector collectors; + + // The first label displays the frame rate. + size_t li = 1; + collectors.push_back(0); + if (_status_bar_labels.empty()) { + // As workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/2029 + // We manually create a GtkFlowBoxChild instance and attach the label to it + // and increase its reference count so it it not prematurely destroyed when + // removed from the GtkFlowBox, causing the app to segfault. + GtkWidget *label = gtk_label_new(""); + GtkWidget *flow_box_child = gtk_flow_box_child_new(); + gtk_container_add(GTK_CONTAINER(flow_box_child), label); + gtk_container_add(GTK_CONTAINER(_status_bar), flow_box_child); + _status_bar_labels.push_back(flow_box_child); + g_object_ref(flow_box_child); + } + + // Gather the top-level collector list. + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, 0)) { + PStatView &view = get_level_view(collector, 0); + view.set_to_frame(frame_data); + double value = view.get_net_value(); + if (value == 0.0) { + // Don't include it unless we've included it before. + if (std::find(_status_bar_collectors.begin(), _status_bar_collectors.end(), collector) == _status_bar_collectors.end()) { + continue; + } + } + + // Add the value for other threads that have this collector. + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + view.set_to_frame(frame_number); + value += view.get_net_value(); + } + + const PStatCollectorDef &def = client_data->get_collector_def(collector); + std::string text = def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + + GtkWidget *flow_box_child; + GtkWidget *label; + if (li < _status_bar_labels.size()) { + flow_box_child = _status_bar_labels[li++]; + label = gtk_bin_get_child(GTK_BIN(flow_box_child)); + gtk_label_set_text(GTK_LABEL(label), text.c_str()); + } + else { + label = gtk_label_new(text.c_str()); + // See comment above + flow_box_child = gtk_flow_box_child_new(); + gtk_container_add(GTK_CONTAINER(flow_box_child), label); + gtk_container_add(GTK_CONTAINER(_status_bar), flow_box_child); + _status_bar_labels.push_back(flow_box_child); + g_object_ref(flow_box_child); + } + + collectors.push_back(collector); + } + } + + _status_bar_collectors = std::move(collectors); + + gtk_widget_show_all(_status_bar); +} + +/** + * Handles clicks on a partion of the status bar. + */ +gboolean GtkStatsMonitor:: +status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { + GtkStatsMonitor *monitor = (GtkStatsMonitor *)data; + + GtkFlowBoxChild *child = gtk_flow_box_get_child_at_pos( + GTK_FLOW_BOX(monitor->_status_bar), event->x, event->y); + if (child == nullptr) { + return FALSE; + } + + // Which child is this? + GList *children = gtk_container_get_children(GTK_CONTAINER(monitor->_status_bar)); + int index = g_list_index(children, child); + g_list_free(children); + if (index < 0 || (size_t)index >= monitor->_status_bar_labels.size()) { + return FALSE; + } + + const PStatClientData *client_data = monitor->get_client_data(); + if (client_data == nullptr) { + return FALSE; + } + + int collector = monitor->_status_bar_collectors[index]; + + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + monitor->open_strip_chart(0, collector, collector != 0); + + // Also open a strip chart for other threads with data for this + // collector. + if (collector != 0) { + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = monitor->get_level_view(collector, thread_index); + if (view.get_net_value() > 0.0) { + monitor->open_strip_chart(thread_index, collector, true); + } + } + } + return TRUE; + } + else if (event->type == GDK_BUTTON_PRESS && event->button == 3 && index > 0) { + PStatView &level_view = monitor->get_level_view(collector, 0); + const PStatViewLevel *view_level = level_view.get_top_level(); + int num_children = view_level->get_num_children(); + if (num_children == 0) { + return FALSE; + } + + GtkWidget *menu = gtk_menu_new(); + + // Reverse the order since the menus are listed from the top down; we want + // to be visually consistent with the graphs, which list these labels from + // the bottom up. + for (int c = num_children - 1; c >= 0; c--) { + const PStatViewLevel *child_level = view_level->get_child(c); + + int child_collector = child_level->get_collector(); + const MenuDef *menu_def = monitor->add_menu({CT_strip_chart, 0, child_collector, -1, true}); + + double value = child_level->get_net_value(); + + const PStatCollectorDef &def = client_data->get_collector_def(child_collector); + std::string text = def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + + GtkWidget *menu_item = gtk_menu_item_new_with_label(text.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + + GtkWidget *flow_box_child = monitor->_status_bar_labels[index]; + GtkWidget *label = gtk_bin_get_child(GTK_BIN(flow_box_child)); + gtk_menu_popup_at_widget(GTK_MENU(menu), label, + GDK_GRAVITY_NORTH_WEST, + GDK_GRAVITY_SOUTH_WEST, nullptr); + return TRUE; + } + return FALSE; +} + +/** + * Callback when a menu item is selected. + */ +void GtkStatsMonitor:: +menu_activate(GtkWidget *widget, gpointer data) { + const MenuDef &menu_def = *(const MenuDef *)data; + GtkStatsMonitor *monitor = menu_def._monitor; + + if (monitor == nullptr) { + return; + } + + switch (menu_def._chart_type) { + case CT_timeline: + monitor->open_timeline(); + break; + + case CT_strip_chart: + monitor->open_strip_chart(menu_def._thread_index, + menu_def._collector_index, + menu_def._show_level); + break; + + case CT_flame_graph: + monitor->open_flame_graph(menu_def._thread_index, + menu_def._collector_index, + menu_def._frame_number); + break; + + case CT_piano_roll: + monitor->open_piano_roll(menu_def._thread_index); + break; + + case CT_choose_color: + monitor->choose_collector_color(menu_def._collector_index); + break; + + case CT_reset_color: + monitor->reset_collector_color(menu_def._collector_index); + break; + } +} + +/** + * Called when a status bar item is double-clicked. + */ +void GtkStatsMonitor:: +handle_status_bar_click(int item) { + if (item == 0) { + open_strip_chart(0, 0, false); + } + else if (item >= 1 && (size_t)item < _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item]; + open_strip_chart(0, collector, true); + + // Also open a strip chart for other threads with data for this + // collector. + const PStatClientData *client_data = get_client_data(); + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + if (view.get_net_value() > 0.0) { + open_strip_chart(thread_index, collector, true); + } + } + } +} + +/** + * Called when a status bar item is right-clicked. + */ +void GtkStatsMonitor:: +handle_status_bar_popup(int item) { + if (item >= 0 && (size_t)item < _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item]; + + PStatView &level_view = get_level_view(collector, 0); + const PStatViewLevel *view_level = level_view.get_top_level(); + int num_children = view_level->get_num_children(); + if (num_children == 0) { + return; + } + + GtkWidget *menu = gtk_menu_new(); + + // Reverse the order since the menus are listed from the top down; we want + // to be visually consistent with the graphs, which list these labels from + // the bottom up. + const PStatClientData *client_data = get_client_data(); + for (int c = num_children - 1; c >= 0; c--) { + const PStatViewLevel *child_level = view_level->get_child(c); + + int child_collector = child_level->get_collector(); + const MenuDef *menu_def = add_menu({CT_strip_chart, 0, child_collector, -1, true}); + + double value = child_level->get_net_value(); + + const PStatCollectorDef &def = client_data->get_collector_def(child_collector); + std::string text = def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + + GtkWidget *menu_item = gtk_menu_item_new_with_label(text.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + + GtkWidget *flow_box_child = _status_bar_labels[item]; + GtkWidget *label = gtk_bin_get_child(GTK_BIN(flow_box_child)); + gtk_menu_popup_at_widget(GTK_MENU(menu), label, + GDK_GRAVITY_NORTH_WEST, + GDK_GRAVITY_SOUTH_WEST, nullptr); + + } +} diff --git a/pandatool/src/gtk-stats/gtkStatsMonitor.h b/pandatool/src/gtk-stats/gtkStatsMonitor.h new file mode 100644 index 00000000..3ac15aba --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsMonitor.h @@ -0,0 +1,148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsMonitor.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSMONITOR_H +#define GTKSTATSMONITOR_H + +#include "pandatoolbase.h" + +#include "pStatMonitor.h" +#include "pointerTo.h" +#include "pset.h" +#include "pvector.h" +#include "pmap.h" + +#include + +class GtkStatsGraph; +class GtkStatsServer; +class GtkStatsChartMenu; + +/** + * This class represents a connection to a PStatsClient and manages the data + * exchange with the client. + */ +class GtkStatsMonitor : public PStatMonitor { +public: + enum ChartType { + CT_timeline, + CT_strip_chart, + CT_flame_graph, + CT_piano_roll, + + CT_choose_color, + CT_reset_color, + }; + + class MenuDef { + public: + INLINE MenuDef(ChartType chart_type, int thread_index, int collector_index, + int frame_number = -1, bool show_level = false); + INLINE bool operator < (const MenuDef &other) const; + + ChartType _chart_type; + int _thread_index; + int _collector_index; + int _frame_number; + bool _show_level; + GtkStatsMonitor *_monitor; + }; + + GtkStatsMonitor(GtkStatsServer *server); + virtual ~GtkStatsMonitor(); + + void close(); + + virtual std::string get_monitor_name(); + + virtual void initialized(); + virtual void got_hello(); + virtual void got_bad_version(int client_major, int client_minor, + int server_major, int server_minor); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + virtual void remove_thread(int thread_index); + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + + virtual void user_guide_bars_changed(); + + GtkWidget *get_window() const; + GtkAccelGroup *get_accel_group() const; + double get_resolution() const; + + PStatGraph *open_timeline(); + PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + PStatGraph *open_flame_graph(int thread_index, int collector_index = -1, int frame_number = -1); + PStatGraph *open_piano_roll(int thread_index); + + void choose_collector_color(int collector_index); + void reset_collector_color(int collector_index); + + const MenuDef *add_menu(const MenuDef &menu_def); + + void set_time_units(int unit_mask); + void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void add_graph(GtkStatsGraph *graph); + void remove_graph(GtkStatsGraph *graph); + void remove_all_graphs(); + +private: + void setup_speed_menu(); + void setup_frame_rate_label(); + void update_status_bar(); + + static gboolean status_bar_button_event(GtkWidget *widget, + GdkEventButton *event, + gpointer data); +public: + static void menu_activate(GtkWidget *widget, gpointer data); + void handle_status_bar_click(int item); + void handle_status_bar_popup(int item); + +private: + typedef pset Graphs; + Graphs _graphs; + + typedef pvector ChartMenus; + ChartMenus _chart_menus; + + typedef pset Menus; + Menus _menus; + + GtkWidget *_window; + GtkWidget *_menu_bar; + GtkWidget *_speed_menu_item = nullptr; + int _next_chart_index; + GtkWidget *_frame_rate_menu_item = nullptr; + GtkWidget *_frame_rate_label; + GtkWidget *_status_bar; + pvector _status_bar_collectors; + pvector _status_bar_labels; + std::string _window_title; + double _scroll_speed; + bool _pause; + bool _have_data = false; + double _resolution; + + friend class GtkStatsGraph; + friend class GtkStatsServer; +}; + +#include "gtkStatsMonitor.I" + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx new file mode 100644 index 00000000..da5e6b36 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.cxx @@ -0,0 +1,642 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsPianoRoll.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsPianoRoll.h" +#include "gtkStatsMonitor.h" +#include "numeric_types.h" +#include "gtkStatsLabelStack.h" + +static const int default_piano_roll_width = 800; +static const int default_piano_roll_height = 400; + +/** + * + */ +GtkStatsPianoRoll:: +GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index) : + PStatPianoRoll(monitor, thread_index, 0, 0), + GtkStatsGraph(monitor, true) +{ + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + // Add a DrawingArea widget on top of the graph, to display all of the scale + // units. + _scale_area = gtk_drawing_area_new(); + g_signal_connect(G_OBJECT(_scale_area), "draw", + G_CALLBACK(draw_callback), this); + gtk_box_pack_start(GTK_BOX(_graph_vbox), _scale_area, FALSE, FALSE, 0); + + // It should be large enough to display the labels. + { + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, "0123456789 ms"); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + gtk_widget_set_size_request(_scale_area, 0, height + 1); + g_object_unref(layout); + } + + gtk_widget_set_size_request(_graph_window, + default_piano_roll_width * monitor->get_resolution() / 96, + default_piano_roll_height * monitor->get_resolution() / 96); + + const PStatClientData *client_data = + GtkStatsGraph::_monitor->get_client_data(); + std::string thread_name = client_data->get_thread_name(_thread_index); + std::string window_title = thread_name + " thread piano roll"; + gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str()); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + + // Allow the window to be resized as small as the user likes. We have to do + // this after the window has been shown; otherwise, it will affect the + // window's initial size. + gtk_widget_set_size_request(_window, 0, 0); + + force_redraw(); + idle(); +} + +/** + * + */ +GtkStatsPianoRoll:: +~GtkStatsPianoRoll() { +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void GtkStatsPianoRoll:: +new_data(int thread_index, int frame_number) { + if (!_pause) { + update(); + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void GtkStatsPianoRoll:: +force_redraw() { + if (_cr) { + PStatPianoRoll::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void GtkStatsPianoRoll:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatPianoRoll::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsPianoRoll:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + gtk_widget_queue_draw(_scale_area); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void GtkStatsPianoRoll:: +on_click_label(int collector_index) { + if (collector_index >= 0) { + GtkStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false); + } +} + +/** + * Called when the user right-clicks on a label. + */ +void GtkStatsPianoRoll:: +on_popup_label(int collector_index) { + GtkWidget *menu = gtk_menu_new(); + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + GtkWidget *menu_item = gtk_menu_item_new_with_label(label.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + gtk_widget_set_sensitive(menu_item, FALSE); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_strip_chart, _thread_index, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Strip Chart"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_flame_graph, _thread_index, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Flame Graph"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_choose_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_reset_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsPianoRoll:: +get_label_tooltip(int collector_index) const { + return PStatPianoRoll::get_label_tooltip(collector_index); +} + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +void GtkStatsPianoRoll:: +set_horizontal_scale(double time_width) { + PStatPianoRoll::set_horizontal_scale(time_width); + + gtk_widget_queue_draw(_graph_window); + gtk_widget_queue_draw(_scale_area); +} + +/** + * Erases the chart area. + */ +void GtkStatsPianoRoll:: +clear_region() { + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_paint(_cr); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void GtkStatsPianoRoll:: +begin_draw() { + clear_region(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_cr, get_guide_bar(i)); + } +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any one row of bars. These bars correspond to the collector whose + * index is get_row_collector(row), and in the color get_row_color(row). + */ +void GtkStatsPianoRoll:: +begin_row(int row) { + int collector_index = get_label_collector(row); + cairo_set_source(_cr, get_collector_pattern(collector_index, + _highlighted_index == collector_index)); +} + +/** + * Draws a single bar on the chart. + */ +void GtkStatsPianoRoll:: +draw_bar(int row, int from_x, int to_x) { + if (row >= 0 && row < _label_stack.get_num_labels()) { + int y = _label_stack.get_label_y(row, _graph_window); + int height = _label_stack.get_label_height(row); + + cairo_rectangle(_cr, from_x, (y - height + 2) * _cr_scale, to_x - from_x, (height - 4) * _cr_scale); + cairo_fill(_cr); + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void GtkStatsPianoRoll:: +end_draw() { + gtk_widget_queue_draw(_graph_window); +} + +/** + * Called at the end of the draw cycle. + */ +void GtkStatsPianoRoll:: +idle() { + if (_labels_changed) { + update_labels(); + } +} + +/** + * Returns the current window dimensions. + */ +bool GtkStatsPianoRoll:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsPianoRoll:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * This is called during the servicing of the draw event; it gives a derived + * class opportunity to do some further painting into the graph window. + */ +void GtkStatsPianoRoll:: +additional_graph_window_paint(cairo_t *cr) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(cr, get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsPianoRoll:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + int collector_index = get_collector_under_pixel(mouse_x, mouse_y); + if (collector_index >= 0) { + return get_label_tooltip(collector_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +GtkStatsGraph::DragMode GtkStatsPianoRoll:: +consider_drag_start(int graph_x, int graph_y) { + if (graph_y >= 0 && graph_y < get_ysize()) { + if (graph_x >= 0 && graph_x < get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = graph_x; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + return GtkStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +gboolean GtkStatsPianoRoll:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + int collector_index = get_collector_under_pixel(graph_x, graph_y); + if (button == 3) { + // Right-clicking on a color bar in the graph is the same as right- + // clicking on the corresponding label. + if (collector_index >= 0) { + on_popup_label(collector_index); + return TRUE; + } + return FALSE; + } + else if (double_click && button == 1) { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + on_click_label(get_collector_under_pixel(graph_x, graph_y)); + return TRUE; + } + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_x); + // SetCapture(_graph_window); + return TRUE; + + } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_x = graph_x; + // SetCapture(_graph_window); + return TRUE; + } + + return GtkStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +gboolean GtkStatsPianoRoll:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return GtkStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +gboolean GtkStatsPianoRoll:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int collector_index = get_collector_under_pixel(graph_x, graph_y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + + /* + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + */ + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + double ratio = (double)graph_x / (double)get_xsize(); + if (ratio > 0.0f) { + set_horizontal_scale(_drag_scale_start / ratio); + } + return TRUE; + } + else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_x >= 0 && graph_x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x)); + return TRUE; + } + + } else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + return TRUE; + } + + return GtkStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsPianoRoll:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + return TRUE; +} + +/** + * Returns the collector index associated with the indicated vertical row, or + * -1. + */ +int GtkStatsPianoRoll:: +get_collector_under_pixel(int xpoint, int ypoint) const { + if (_label_stack.get_num_labels() == 0) { + return -1; + } + + // Assume all of the labels are the same height. + int height = _label_stack.get_label_height(0); + int row = (get_ysize() - ypoint) / (height * _cr_scale); + if (row >= 0 && row < _label_stack.get_num_labels()) { + return _label_stack.get_label_collector_index(row); + } else { + return -1; + } +} + +/** + * Resets the list of labels. + */ +void GtkStatsPianoRoll:: +update_labels() { + _label_stack.clear_labels(); + for (int i = 0; i < get_num_labels(); i++) { + _label_stack.add_label(GtkStatsGraph::_monitor, this, + _thread_index, + get_label_collector(i), true); + } + _labels_changed = false; +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void GtkStatsPianoRoll:: +draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to the top. + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + cairo_move_to(cr, x, 0); + cairo_line_to(cr, x, get_ysize()); + cairo_stroke(cr); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void GtkStatsPianoRoll:: +draw_guide_labels(cairo_t *cr) { + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + draw_guide_label(cr, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + draw_guide_label(cr, get_user_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void GtkStatsPianoRoll:: +draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar) { + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, label.c_str()); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - width * _cr_scale); + double to_height = pixel_to_height(x + width * _cr_scale); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + g_object_unref(layout); + return; + } + } + + if (x >= 0 && x < get_xsize()) { + // Now convert our x to a coordinate within our drawing area. + int junk_y; + + x /= _cr_scale; + + // The x coordinate comes from the graph_window. + gtk_widget_translate_coordinates(_graph_window, _scale_area, + x, 0, + &x, &junk_y); + + GtkAllocation allocation; + gtk_widget_get_allocation(_scale_area, &allocation); + + int this_x = x - width / 2; + cairo_move_to(cr, this_x, allocation.height - height); + pango_cairo_show_layout(cr, layout); + } + + g_object_unref(layout); +} + +/** + * Draws in the scale labels. + */ +gboolean GtkStatsPianoRoll:: +draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsPianoRoll *self = (GtkStatsPianoRoll *)data; + self->draw_guide_labels(cr); + + return TRUE; +} diff --git a/pandatool/src/gtk-stats/gtkStatsPianoRoll.h b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h new file mode 100644 index 00000000..e3dd892d --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsPianoRoll.h @@ -0,0 +1,79 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsPianoRoll.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSPIANOROLL_H +#define GTKSTATSPIANOROLL_H + +#include "pandatoolbase.h" + +#include "gtkStatsGraph.h" +#include "pStatPianoRoll.h" +#include "pointerTo.h" + +#include + +class GtkStatsMonitor; + +/** + * A window that draws a piano-roll style chart, which shows the collectors + * explicitly stopping and starting, one frame at a time. + */ +class GtkStatsPianoRoll final : public PStatPianoRoll, public GtkStatsGraph { +public: + GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index); + virtual ~GtkStatsPianoRoll(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + void set_horizontal_scale(double time_width); + +protected: + void clear_region(); + virtual void begin_draw(); + virtual void begin_row(int row); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_draw(); + virtual void idle(); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual void additional_graph_window_paint(cairo_t *cr); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual gboolean handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual gboolean handle_button_release(int graph_x, int graph_y); + virtual gboolean handle_motion(int graph_x, int graph_y); + virtual gboolean handle_leave(); + +private: + int get_collector_under_pixel(int xpoint, int ypoint) const; + void update_labels(); + void draw_guide_bar(cairo_t *cr, const PStatGraph::GuideBar &bar); + void draw_guide_labels(cairo_t *cr); + void draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar); + + static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data); +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsServer.cxx b/pandatool/src/gtk-stats/gtkStatsServer.cxx new file mode 100644 index 00000000..25fdba2e --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsServer.cxx @@ -0,0 +1,766 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsServer.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsServer.h" +#include "gtkStatsMonitor.h" +#include "pandaVersion.h" +#include "pStatGraph.h" +#include "config_pstatclient.h" + +#include + +/** + * + */ +GtkStatsServer:: +GtkStatsServer() : _port(pstats_port) { + set_program_brief("GTK+3-based PStats client"); + set_program_description + ("This is a GUI-based PStats server that listens on a TCP port for a " + "connection from a PStatClient in a Panda3D application. It offers " + "various graphs for showing the timing information sent by the client." + "\n\n" + "The full documentation is available online:\n " +#ifdef HAVE_PYTHON + "https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/python/optimization/pstats" +#else + "https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/cpp/optimization/pstats" +#endif + ""); + + add_option + ("p", "port", 0, + "Specify the TCP port to listen for connections on. By default, this " + "is taken from the pstats-port Config variable.", + &ProgramBase::dispatch_int, nullptr, &_port); + + add_runline("[-p 5185]"); + add_runline("session.pstats"); + +#ifdef __APPLE__ + _last_session = Filename::expand_from( + "$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats"); +#else + _last_session = Filename::expand_from("$XDG_STATE_HOME/panda3d/last-session.pstats"); +#endif + _last_session.set_binary(); + + create_window(); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool GtkStatsServer:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + new_session(); + return true; + } + else if (args.size() == 1) { + Filename fn = Filename::from_os_specific(args[0]); + fn.set_binary(); + GtkStatsMonitor *monitor = new GtkStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + // If we're not running from the terminal, show a GUI message box. + if (!isatty(STDERR_FILENO)) { + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to load session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } + return false; + } + _save_filename = fn; + + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + _monitor = monitor; + return true; + } + else { + nout << "At most one filename may be specified on the command-line.\n"; + return false; + } +} + +/** + * + */ +PStatMonitor *GtkStatsServer:: +make_monitor(const NetAddress &address) { + // Enable the "New Session", "Save Session" and "Close Session" menu items. + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + std::ostringstream strm; + strm << "PStats Server (connected to " << address << ")"; + std::string title = strm.str(); + gtk_window_set_title(GTK_WINDOW(_window), title.c_str()); + + if (_status_bar_label != nullptr) { + gtk_container_remove(GTK_CONTAINER(_status_bar), _status_bar_label); + g_object_unref(_status_bar_label); + _status_bar_label = nullptr; + } + + _monitor = new GtkStatsMonitor(this); + return _monitor; +} + +/** + * Called when connection has been lost. + */ +void GtkStatsServer:: +lost_connection(PStatMonitor *monitor) { + if (_monitor != nullptr && !_monitor->_have_data) { + // We didn't have any data yet. Just silently restart the session. + _monitor->close(); + _monitor = nullptr; + if (new_session()) { + return; + } + } else { + // Store a backup now, in case PStats crashes or something. + _last_session.make_dir(); + if (monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + } else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + stop_listening(); + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server (disconnected)"); +} + +/** + * Starts a new session. + */ +bool GtkStatsServer:: +new_session() { + if (!close_session()) { + return false; + } + + if (listen(_port)) { + { + std::ostringstream strm; + strm << "PStats Server (listening on port " << _port << ")"; + std::string title = strm.str(); + gtk_window_set_title(GTK_WINDOW(_window), title.c_str()); + } + { + std::ostringstream strm; + strm << "Waiting for client to connect on port " << _port << "..."; + std::string title = strm.str(); + // As workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/2029 + // We manually create a GtkFlowBoxChild instance and attach the label to it + // and increase its reference count so it it not prematurely destroyed when + // removed from the GtkFlowBox, causing the app to segfault. + GtkWidget * label = gtk_label_new(title.c_str()); + _status_bar_label = gtk_flow_box_child_new(); + gtk_container_add(GTK_CONTAINER(_status_bar_label), label); + gtk_container_add(GTK_CONTAINER(_status_bar), _status_bar_label); + gtk_widget_show(_status_bar_label); + g_object_ref(_status_bar_label); + } + + gtk_widget_set_sensitive(_new_session_menu_item, FALSE); + gtk_widget_set_sensitive(_save_session_menu_item, FALSE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, FALSE); + + return true; + } + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Unable to open port %d. Try specifying a different port number " + "using pstats-port in your Config file or the -p option on the " + "command-line.", _port); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return false; +} + +/** + * Offers to open an existing session. + */ +bool GtkStatsServer:: +open_session() { + if (!close_session()) { + return false; + } + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Open Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Open", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "PStats Session Files"); + gtk_file_filter_add_pattern(filter, "*.pstats"); + gtk_file_chooser_add_filter(chooser, filter); + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser)); + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + g_free(buffer); + g_object_unref(native); + + GtkStatsMonitor *monitor = new GtkStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to load session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + _save_filename = fn; + + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + _monitor = monitor; + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Opens the last session, if any. + */ +bool GtkStatsServer:: +open_last_session() { + if (!close_session()) { + return false; + } + + Filename fn = _last_session; + GtkStatsMonitor *monitor = new GtkStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to load session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + gtk_widget_set_sensitive(_save_session_menu_item, TRUE); + gtk_widget_set_sensitive(_close_session_menu_item, TRUE); + gtk_widget_set_sensitive(_export_session_menu_item, TRUE); + + // If the file contained no graphs, open the default graphs. + if (monitor->_graphs.empty()) { + monitor->open_default_graphs(); + } + + return true; +} + +/** + * Offers to save the current session. + */ +bool GtkStatsServer:: +save_session() { + nassertr_always(_monitor != nullptr, true); + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Save Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Save", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "PStats Session Files"); + gtk_file_filter_add_pattern(filter, "*.pstats"); + gtk_file_chooser_add_filter(chooser, filter); + + if (_save_filename.empty()) { + gtk_file_chooser_set_current_name(chooser, "session.pstats"); + } + else { + gtk_file_chooser_set_filename(chooser, _save_filename.c_str()); + } + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(chooser); + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + g_free(buffer); + g_object_unref(native); + + if (!_monitor->write(fn)) { + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to save session file: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + _save_filename = fn; + _monitor->get_client_data()->clear_dirty(); + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Offers to export the current session as a JSON file. + */ +bool GtkStatsServer:: +export_session() { + nassertr_always(_monitor != nullptr, true); + + GtkFileChooserNative *native = gtk_file_chooser_native_new( + "Export Session", + GTK_WINDOW(_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Export", "Cancel"); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(native); + + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "JSON files"); + gtk_file_filter_add_pattern(filter, "*.json"); + gtk_file_chooser_add_filter(chooser, filter); + + gtk_file_chooser_set_current_name(chooser, "session.json"); + + gint res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *buffer = gtk_file_chooser_get_filename(chooser); + Filename fn = Filename::from_os_specific(buffer); + fn.set_text(); + g_free(buffer); + g_object_unref(native); + + std::ofstream stream; + if (!fn.open_write(stream)) { + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to open file for export: %s", fn.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return false; + } + + int pid = _monitor->get_client_pid(); + _monitor->get_client_data()->write_json(stream, std::max(0, pid)); + stream.close(); + return true; + } + + g_object_unref(native); + return false; +} + +/** + * Closes the current session. + */ +bool GtkStatsServer:: +close_session() { + bool wrote_last_session = false; + + if (_monitor != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->is_dirty()) { + if (!_monitor->has_read_filename()) { + _last_session.make_dir(); + if (_monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + wrote_last_session = true; + } + else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + GtkWidget *dialog = + gtk_message_dialog_new(GTK_WINDOW(_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Would you like to save the currently open session?"); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response == GTK_RESPONSE_CANCEL || + (response == GTK_RESPONSE_YES && !save_session())) { + return false; + } + } + + _monitor->close(); + _monitor = nullptr; + } + + _save_filename = Filename(); + stop_listening(); + + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + + if (_status_bar_label != nullptr) { + gtk_container_remove(GTK_CONTAINER(_status_bar), _status_bar_label); + g_object_unref(_status_bar_label); + _status_bar_label = nullptr; + } + + gtk_widget_set_sensitive(_new_session_menu_item, TRUE); + if (wrote_last_session) { + gtk_widget_set_sensitive(_open_last_session_menu_item, TRUE); + } + gtk_widget_set_sensitive(_save_session_menu_item, FALSE); + gtk_widget_set_sensitive(_close_session_menu_item, FALSE); + gtk_widget_set_sensitive(_export_session_menu_item, FALSE); + return true; +} + +/** + * Returns the window handle to the server's window. + */ +GtkWidget *GtkStatsServer:: +get_window() const { + return _window; +} + +/** + * Returns the server window's accelerator group. + */ +GtkAccelGroup *GtkStatsServer:: +get_accel_group() const { + return _accel_group; +} + +/** + * Returns the menu handle to the server's menu bar. + */ +GtkWidget *GtkStatsServer:: +get_menu_bar() const { + return _menu_bar; +} + +/** + * Returns the window handle to the server's status bar. + */ +GtkWidget *GtkStatsServer:: +get_status_bar() const { + return _status_bar; +} + +/** + * + */ +int GtkStatsServer:: +get_time_units() const { + return _time_units; +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsServer:: +set_time_units(int unit_mask) { + _time_units = unit_mask; + + if (_monitor != nullptr) { + _monitor->set_time_units(unit_mask); + } +} + +/** + * Creates the window for this monitor. + */ +void GtkStatsServer:: +create_window() { + _window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(_window), "PStats Server"); + gtk_window_set_default_size(GTK_WINDOW(_window), 500, 360); + + // Connect the delete and destroy events, so the user can exit the + // application by closing the main window. + g_signal_connect(G_OBJECT(_window), "delete_event", + G_CALLBACK(+[](GtkWidget *widget, GdkEvent *event, gpointer data) -> gboolean { + GtkStatsServer *self = (GtkStatsServer *)data; + return self->close_session() ? FALSE : TRUE; + }), this); + + g_signal_connect(G_OBJECT(_window), "destroy", + G_CALLBACK(+[](GtkWidget *widget, GdkEvent *event, gpointer data) -> gboolean { + gtk_main_quit(); + return FALSE; + }), this); + + // Set up the menu. + _accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(_window), _accel_group); + _menu_bar = gtk_menu_bar_new(); + + setup_session_menu(); + setup_options_menu(); + + // Pack the menu into the window. + GtkWidget *main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 1); + gtk_container_add(GTK_CONTAINER(_window), main_vbox); + gtk_box_pack_start(GTK_BOX(main_vbox), _menu_bar, FALSE, TRUE, 0); + + // Create the status bar. + _status_bar = gtk_flow_box_new(); + gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(_status_bar), FALSE); + gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(_status_bar), GTK_SELECTION_NONE); + g_signal_connect(G_OBJECT(_status_bar), "button_press_event", + G_CALLBACK(status_bar_button_event), this); + gtk_box_pack_end(GTK_BOX(main_vbox), _status_bar, FALSE, FALSE, 0); + + GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_box_pack_end(GTK_BOX(main_vbox), sep, FALSE, FALSE, 0); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + gtk_widget_realize(_window); + + // Set up a timer to poll the pstats every so often. + g_timeout_add(200, +[](gpointer data) -> gboolean { + GtkStatsServer *self = (GtkStatsServer *)data; + self->poll(); + return TRUE; + }, this); +} + +/** + * Creates the "Session" pulldown menu. + */ +void GtkStatsServer:: +setup_session_menu() { + _session_menu = gtk_menu_new(); + + GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Session"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _session_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); + + item = gtk_menu_item_new_with_mnemonic("_New Session"); + _new_session_menu_item = item; + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->new_session(); + }), this); + gtk_widget_add_accelerator(item, "activate", _accel_group, + GDK_KEY_n, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + item = gtk_menu_item_new_with_mnemonic("_Open Session..."); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->open_session(); + }), this); + gtk_widget_add_accelerator(item, "activate", _accel_group, + GDK_KEY_o, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + item = gtk_menu_item_new_with_mnemonic("Open _Last Session"); + _open_last_session_menu_item = item; + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->open_last_session(); + }), this); + + if (!_last_session.exists()) { + gtk_widget_set_sensitive(item, FALSE); + } + + item = gtk_menu_item_new_with_mnemonic("_Save Session..."); + _save_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->save_session(); + }), this); + gtk_widget_add_accelerator(item, "activate", _accel_group, + GDK_KEY_s, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + item = gtk_menu_item_new_with_mnemonic("_Close Session"); + _close_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->close_session(); + }), this); + gtk_widget_add_accelerator(item, "activate", _accel_group, + GDK_KEY_w, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + GtkWidget *sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), sep); + + item = gtk_menu_item_new_with_mnemonic("_Export as JSON..."); + _export_session_menu_item = item; + gtk_widget_set_sensitive(item, FALSE); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->export_session(); + }), this); + + sep = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), sep); + + item = gtk_menu_item_new_with_mnemonic("E_xit"); + gtk_menu_shell_append(GTK_MENU_SHELL(_session_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + if (self->close_session()) { + gtk_main_quit(); + } + }), this); + gtk_widget_add_accelerator(item, "activate", _accel_group, + GDK_KEY_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + + gtk_widget_show_all(_session_menu); +} + +/** + * Creates the "Options" pulldown menu. + */ +void GtkStatsServer:: +setup_options_menu() { + _options_menu = gtk_menu_new(); + + GtkWidget *item = gtk_menu_item_new_with_label("Options"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), _options_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_menu_bar), item); + + GtkWidget *units_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_label("Units"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), units_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(_options_menu), item); + + item = gtk_radio_menu_item_new_with_label(nullptr, "ms"); + gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->set_time_units(PStatGraph::GBU_ms); + }), this); + + item = gtk_radio_menu_item_new_with_label( + gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item)), "Hz"); + gtk_menu_shell_append(GTK_MENU_SHELL(units_menu), item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(+[](GtkMenuItem *item, gpointer data) { + GtkStatsServer *self = (GtkStatsServer *)data; + self->set_time_units(PStatGraph::GBU_hz); + }), this); + + set_time_units(PStatGraph::GBU_ms); +} + +/** + * Handles clicks on a partion of the status bar. + */ +gboolean GtkStatsServer:: +status_bar_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { + GtkStatsServer *server = (GtkStatsServer *)data; + + GtkFlowBoxChild *child = gtk_flow_box_get_child_at_pos( + GTK_FLOW_BOX(server->_status_bar), event->x, event->y); + if (child == nullptr) { + return FALSE; + } + + // Which child is this? + GList *children = gtk_container_get_children(GTK_CONTAINER(server->_status_bar)); + int index = g_list_index(children, child); + g_list_free(children); + if (index < 0) { + return FALSE; + } + + if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + server->_monitor->handle_status_bar_click(index); + return TRUE; + } + else if (event->type == GDK_BUTTON_PRESS && event->button == 3 && index > 0) { + server->_monitor->handle_status_bar_popup(index); + return TRUE; + } + return FALSE; +} diff --git a/pandatool/src/gtk-stats/gtkStatsServer.h b/pandatool/src/gtk-stats/gtkStatsServer.h new file mode 100644 index 00000000..9aa3507a --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsServer.h @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsServer.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSSERVER_H +#define GTKSTATSSERVER_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "pStatServer.h" +#include "gtkStatsMonitor.h" + +/** + * The class that owns the main loop, waiting for client connections. + */ +class GtkStatsServer : public PStatServer, public ProgramBase { +public: + GtkStatsServer(); + + virtual bool handle_args(Args &args) override; + + virtual PStatMonitor *make_monitor(const NetAddress &address) override; + virtual void lost_connection(PStatMonitor *monitor) override; + + bool new_session(); + bool open_session(); + bool open_last_session(); + bool save_session(); + bool export_session(); + bool close_session(); + + GtkWidget *get_window() const; + GtkAccelGroup *get_accel_group() const; + GtkWidget *get_menu_bar() const; + GtkWidget *get_status_bar() const; + + int get_time_units() const; + void set_time_units(int unit_mask); + +private: + void create_window(); + void setup_session_menu(); + void setup_options_menu(); + + static gboolean status_bar_button_event(GtkWidget *widget, + GdkEventButton *event, + gpointer data); + +private: + PT(GtkStatsMonitor) _monitor; + + Filename _last_session; + Filename _save_filename; + + int _port = -1; + GtkWidget *_window = nullptr; + GtkAccelGroup *_accel_group = nullptr; + GtkWidget *_menu_bar = nullptr; + GtkWidget *_session_menu = nullptr; + GtkWidget *_options_menu = nullptr; + GtkWidget *_status_bar; + GtkWidget *_status_bar_label = nullptr; + GtkWidget *_new_session_menu_item; + GtkWidget *_open_last_session_menu_item; + GtkWidget *_save_session_menu_item; + GtkWidget *_close_session_menu_item; + GtkWidget *_export_session_menu_item; + int _time_units = 0; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.cxx b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx new file mode 100644 index 00000000..0be191da --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.cxx @@ -0,0 +1,796 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsStripChart.cxx + * @author drose + * @date 2006-01-16 + */ + +#include "gtkStatsStripChart.h" +#include "gtkStatsMonitor.h" +#include "pStatCollectorDef.h" +#include "numeric_types.h" +#include "string_utils.h" + +static const int default_strip_chart_width = 400; +static const int default_strip_chart_height = 100; + +/** + * + */ +GtkStatsStripChart:: +GtkStatsStripChart(GtkStatsMonitor *monitor, int thread_index, + int collector_index, bool show_level) : + PStatStripChart(monitor, thread_index, collector_index, show_level, 0, 0), + GtkStatsGraph(monitor, true) +{ + if (show_level) { + // If it's a level-type graph, show the appropriate units. + if (_unit_name.empty()) { + set_guide_bar_units(GBU_named); + } else { + set_guide_bar_units(GBU_named | GBU_show_units); + } + + } else { + // If it's a time-type graph, show the msHz units. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + } + + // Put some stuff on top of the graph. + _top_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(_graph_vbox), _top_hbox, FALSE, FALSE, 0); + + _smooth_check_box = gtk_check_button_new_with_label("Smooth"); + g_signal_connect(G_OBJECT(_smooth_check_box), "toggled", + G_CALLBACK(toggled_callback), this); + + _total_label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(_top_hbox), _smooth_check_box, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(_top_hbox), _total_label, FALSE, FALSE, 0); + + // Add a DrawingArea widget to the right of the graph, to display all of the + // scale units. + _scale_area = gtk_drawing_area_new(); + g_signal_connect(G_OBJECT(_scale_area), "draw", + G_CALLBACK(draw_callback), this); + gtk_box_pack_start(GTK_BOX(_graph_hbox), _scale_area, FALSE, FALSE, 0); + + // Make it wide enough to display a typical label. + { + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, "99 ms"); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + gtk_widget_set_size_request(_scale_area, width, 0); + g_object_unref(layout); + } + + gtk_widget_set_size_request(_graph_window, + default_strip_chart_width * monitor->get_resolution() / 96, + default_strip_chart_height * monitor->get_resolution() / 96); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + + // Allow the window to be resized as small as the user likes. We have to do + // this after the window has been shown; otherwise, it will affect the + // window's initial size. + gtk_widget_set_size_request(_graph_window, 0, 0); + + clear_region(); + + // Update window title and total label. + new_data(0, 0); +} + +/** + * + */ +GtkStatsStripChart:: +~GtkStatsStripChart() { +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void GtkStatsStripChart:: +new_collector(int collector_index) { + GtkStatsGraph::new_collector(collector_index); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void GtkStatsStripChart:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str()); + } + } + + if (!_pause) { + update(); + + std::string text = get_total_text(); + if (_net_value_text != text) { + _net_value_text = text; + gtk_label_set_text(GTK_LABEL(_total_label), _net_value_text.c_str()); + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void GtkStatsStripChart:: +force_redraw() { + if (_cr) { + PStatStripChart::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void GtkStatsStripChart:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatStripChart::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void GtkStatsStripChart:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + gtk_widget_queue_draw(_scale_area); + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void GtkStatsStripChart:: +set_scroll_speed(double scroll_speed) { + // The speed factor indicates chart widths per minute. + if (scroll_speed != 0.0f) { + set_horizontal_scale(60.0f / scroll_speed); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void GtkStatsStripChart:: +on_click_label(int collector_index) { + if (collector_index < 0) { + // Clicking on whitespace in the graph is the same as clicking on the top + // label. + collector_index = get_collector_index(); + } + + if (collector_index == get_collector_index() && collector_index != 0) { + // Clicking on the top label means to go up to the parent level. + const PStatClientData *client_data = + GtkStatsGraph::_monitor->get_client_data(); + if (client_data->has_collector(collector_index)) { + const PStatCollectorDef &def = + client_data->get_collector_def(collector_index); + if (def._parent_index == 0 && get_view().get_show_level()) { + // Unless the parent is "Frame", and we're not a time collector. + } else { + set_collector_index(def._parent_index); + } + } + + } else { + // Clicking on any other label means to focus on that. + set_collector_index(collector_index); + } + + // Update window title and total label. + new_data(0, 0); +} + +/** + * Called when the user right-clicks on a label. + */ +void GtkStatsStripChart:: +on_popup_label(int collector_index) { + GtkWidget *menu = gtk_menu_new(); + _popup_index = collector_index; + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + GtkWidget *menu_item = gtk_menu_item_new_with_label(label.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + gtk_widget_set_sensitive(menu_item, FALSE); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Set as Focus"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + if (collector_index == 0 && get_collector_index() == 0) { + gtk_widget_set_sensitive(menu_item, FALSE); + } else { + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(+[] (GtkWidget *widget, gpointer data) { + GtkStatsStripChart *self = (GtkStatsStripChart *)data; + self->set_collector_index(self->_popup_index); + }), + this); + } + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_strip_chart, _thread_index, collector_index, -1, + get_view().get_show_level(), + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Strip Chart"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + if (!get_view().get_show_level()) { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_flame_graph, _thread_index, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Flame Graph"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_choose_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_reset_color, -1, collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsStripChart:: +get_label_tooltip(int collector_index) const { + return PStatStripChart::get_label_tooltip(collector_index); +} + +/** + * Changes the value the height of the vertical axis represents. This may + * force a redraw. + */ +void GtkStatsStripChart:: +set_vertical_scale(double value_height) { + PStatStripChart::set_vertical_scale(value_height); + + gtk_widget_queue_draw(_graph_window); + gtk_widget_queue_draw(_scale_area); +} + +/** + * Resets the list of labels. + */ +void GtkStatsStripChart:: +update_labels() { + PStatStripChart::update_labels(); + + _label_stack.clear_labels(); + for (int i = 0; i < get_num_labels(); i++) { + _label_stack.add_label(GtkStatsGraph::_monitor, this, _thread_index, + get_label_collector(i), false); + } + _labels_changed = false; +} + +/** + * Erases the chart area. + */ +void GtkStatsStripChart:: +clear_region() { + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_paint(_cr); +} + +/** + * Should be overridden by the user class to copy a region of the chart from + * one part of the chart to another. This is used to implement scrolling. + */ +void GtkStatsStripChart:: +copy_region(int start_x, int end_x, int dest_x) { + // We are not allowed to copy a surface onto itself, so we have to create a + // temporary surface to copy to. + end_x = std::min(end_x, get_xsize()); + GdkWindow *window = gtk_widget_get_window(_graph_window); + cairo_surface_t *temp_surface = + gdk_window_create_similar_image_surface(window, CAIRO_FORMAT_RGB24, end_x - start_x, get_ysize(), 1); + { + cairo_t *temp_cr = cairo_create(temp_surface); + cairo_set_source_surface(temp_cr, _cr_surface, -start_x, 0); + cairo_paint(temp_cr); + cairo_destroy(temp_cr); + } + + cairo_set_source_surface(_cr, temp_surface, 0, 0); + cairo_rectangle(_cr, dest_x, 0, end_x - start_x, get_ysize()); + cairo_fill(_cr); + + cairo_surface_destroy(temp_surface); + + gdk_window_invalidate_rect(window, nullptr, FALSE); +} + +/** + * Draws a single vertical slice of the strip chart, at the given pixel + * position, and corresponding to the indicated level data. + */ +void GtkStatsStripChart:: +draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) { + // Start by clearing the band first. + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_rectangle(_cr, x, 0, w, get_ysize()); + cairo_fill(_cr); + + double overall_time = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + overall_time += cd._net_value; + cairo_set_source(_cr, get_collector_pattern(cd._collector_index, + _highlighted_index == cd._collector_index)); + + if (overall_time > get_vertical_scale()) { + // Off the top. Go ahead and clamp it by hand, in case it's so far off + // the top we'd overflow the 16-bit pixel value. + cairo_rectangle(_cr, x, 0, w, y); + cairo_fill(_cr); + // And we can consider ourselves done now. + return; + } + + int top_y = height_to_pixel(overall_time); + cairo_rectangle(_cr, x, top_y, w, y - top_y); + cairo_fill(_cr); + y = top_y; + } +} + +/** + * Draws a single vertical slice of background color. + */ +void GtkStatsStripChart:: +draw_empty(int x, int w) { + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_rectangle(_cr, x, 0, w, get_ysize()); +} + +/** + * Draws a single vertical slice of foreground color. + */ +void GtkStatsStripChart:: +draw_cursor(int x) { + cairo_set_source_rgb(_cr, 0.0, 0.0, 0.0); + cairo_move_to(_cr, x, 0); + cairo_line_to(_cr, x, get_ysize()); + cairo_stroke(_cr); +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the strip chart; it gives the pixel range + * that was just redrawn. + */ +void GtkStatsStripChart:: +end_draw(int from_x, int to_x) { + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_cr, from_x, to_x, get_guide_bar(i)); + } + + GdkWindow *window = gtk_widget_get_window(_graph_window); + int scale = gdk_window_get_scale_factor(window); + GdkRectangle rect = { + (from_x * scale) / scale, 0, (to_x - from_x) / scale, get_ysize() / scale + }; + gdk_window_invalidate_rect(window, &rect, FALSE); +} + +/** + * Returns the current window dimensions. + */ +bool GtkStatsStripChart:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsStripChart:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * This is called during the servicing of the draw event; it gives a derived + * class opportunity to do some further painting into the graph window. + */ +void GtkStatsStripChart:: +additional_graph_window_paint(cairo_t *cr) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(cr, 0, get_xsize(), get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsStripChart:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + if (_highlighted_index != -1) { + return get_label_tooltip(_highlighted_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +GtkStatsGraph::DragMode GtkStatsStripChart:: +consider_drag_start(int graph_x, int graph_y) { + if (graph_x >= 0 && graph_x < get_xsize()) { + if (graph_y >= 0 && graph_y < get_ysize()) { + // See if the mouse is over a user-defined guide bar. + int y = graph_y; + double from_height = pixel_to_height(y + 2); + double to_height = pixel_to_height(y - 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is above or below the graph; maybe create a new guide bar. + return DM_new_guide_bar; + } + } + + return GtkStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void GtkStatsStripChart:: +set_drag_mode(GtkStatsGraph::DragMode drag_mode) { + GtkStatsGraph::set_drag_mode(drag_mode); + + if (_drag_mode == DM_none) { + // Restore smoothing according to the current setting of the check box. + bool active = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(_smooth_check_box)); + set_average_mode(active); + } +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +gboolean GtkStatsStripChart:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + int collector_index = get_collector_under_pixel(graph_x, graph_y); + if (button == 3) { + // Right-clicking on a color bar in the graph is the same as right- + // clicking on the corresponding label. + if (collector_index >= 0) { + on_popup_label(collector_index); + return TRUE; + } + return FALSE; + } + else if (double_click && button == 1) { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + on_click_label(get_collector_under_pixel(graph_x, graph_y)); + return TRUE; + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_y); + // SetCapture(_graph_window); + return TRUE; + } + } + + if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_y = graph_y; + // SetCapture(_graph_window); + return TRUE; + } + + return GtkStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +gboolean GtkStatsStripChart:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_y < 0 || graph_y >= get_ysize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return GtkStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +gboolean GtkStatsStripChart:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int collector_index = get_collector_under_pixel(graph_x, graph_y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + double ratio = 1.0 - ((double)graph_y / (double)get_ysize()); + if (ratio > 0.0) { + double new_scale = _drag_scale_start / ratio; + if (!IS_NEARLY_EQUAL(get_vertical_scale(), new_scale)) { + // Disable smoothing while we do this expensive operation. + set_average_mode(false); + set_vertical_scale(_drag_scale_start / ratio); + } + } + return TRUE; + } + else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_y >= 0 && graph_y < get_ysize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_y)); + return TRUE; + } + } + else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y)); + return TRUE; + } + + return GtkStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsStripChart:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + return TRUE; +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void GtkStatsStripChart:: +draw_guide_bar(cairo_t *cr, int from_x, int to_x, + const PStatGraph::GuideBar &bar) { + int y = height_to_pixel(bar._height); + + if (y > 0) { + // Only draw it if it's not too close to the top. + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + cairo_move_to(cr, from_x, y); + cairo_line_to(cr, to_x, y); + cairo_stroke(cr); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void GtkStatsStripChart:: +draw_guide_labels(cairo_t *cr) { + // Draw in the labels for the guide bars. + int last_y = -100; + + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + last_y = draw_guide_label(cr, get_guide_bar(i), last_y); + } + + GuideBar top_value = make_guide_bar(get_vertical_scale()); + draw_guide_label(cr, top_value, last_y); + + last_y = -100; + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + last_y = draw_guide_label(cr, get_user_guide_bar(i), last_y); + } +} + +/** + * Draws the text for the indicated guide bar label to the right of the graph, + * unless it would overlap with the indicated last label, whose top pixel + * value is given. Returns the top pixel value of the new label. + */ +int GtkStatsStripChart:: +draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar, int last_y) { + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + default: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + break; + } + + int y = height_to_pixel(bar._height); + const std::string &label = bar._label; + + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, label.c_str()); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(y + height * _cr_scale); + double to_height = pixel_to_height(y - height * _cr_scale); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + g_object_unref(layout); + return last_y; + } + } + + if (y >= 0 && y < get_ysize()) { + // Now convert our y to a coordinate within our drawing area. + int junk_x; + + y /= _cr_scale; + + // The y coordinate comes from the graph_window. + gtk_widget_translate_coordinates(_graph_window, _scale_area, + 0, y, + &junk_x, &y); + + int this_y = y - height / 2; + if (last_y < this_y || last_y > this_y + height) { + cairo_move_to(cr, 0, this_y); + pango_cairo_show_layout(cr, layout); + last_y = this_y; + } + } + + g_object_unref(layout); + return last_y; +} + +/** + * Called when the smooth check box is toggled. + */ +void GtkStatsStripChart:: +toggled_callback(GtkToggleButton *button, gpointer data) { + GtkStatsStripChart *self = (GtkStatsStripChart *)data; + + bool active = gtk_toggle_button_get_active(button); + self->set_average_mode(active); +} + +/** + * Draws in the scale labels. + */ +gboolean GtkStatsStripChart:: +draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsStripChart *self = (GtkStatsStripChart *)data; + self->draw_guide_labels(cr); + + return TRUE; +} diff --git a/pandatool/src/gtk-stats/gtkStatsStripChart.h b/pandatool/src/gtk-stats/gtkStatsStripChart.h new file mode 100644 index 00000000..687c3e70 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsStripChart.h @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsStripChart.h + * @author drose + * @date 2006-01-16 + */ + +#ifndef GTKSTATSSTRIPCHART_H +#define GTKSTATSSTRIPCHART_H + +#include "pandatoolbase.h" + +#include "gtkStatsGraph.h" +#include "pStatStripChart.h" +#include "pointerTo.h" + +#include + +class GtkStatsMonitor; + +/** + * A window that draws a strip chart, given a view. + */ +class GtkStatsStripChart final : public PStatStripChart, public GtkStatsGraph { +public: + GtkStatsStripChart(GtkStatsMonitor *monitor, + int thread_index, int collector_index, bool show_level); + virtual ~GtkStatsStripChart(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + void set_vertical_scale(double value_height); + +protected: + virtual void update_labels(); + + virtual void clear_region(); + virtual void copy_region(int start_x, int end_x, int dest_x); + virtual void draw_slice(int x, int w, + const PStatStripChart::FrameData &fdata); + virtual void draw_empty(int x, int w); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual void additional_graph_window_paint(cairo_t *cr); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + virtual void set_drag_mode(DragMode drag_mode); + + virtual gboolean handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual gboolean handle_button_release(int graph_x, int graph_y); + virtual gboolean handle_motion(int graph_x, int graph_y); + virtual gboolean handle_leave(); + +private: + void draw_guide_bar(cairo_t *cr, int from_x, int to_x, + const PStatGraph::GuideBar &bar); + void draw_guide_labels(cairo_t *cr); + int draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar, int last_y); + + static void toggled_callback(GtkToggleButton *button, gpointer data); + static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data); + +private: + std::string _net_value_text; + + GtkWidget *_top_hbox; + GtkWidget *_smooth_check_box; + GtkWidget *_total_label; + + int _popup_index = -1; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.cxx b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx new file mode 100644 index 00000000..bffdd32e --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.cxx @@ -0,0 +1,929 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsTimeline.cxx + * @author rdb + * @date 2022-02-17 + */ + +#include "gtkStatsTimeline.h" +#include "gtkStatsMonitor.h" +#include "numeric_types.h" +#include "gtkStatsLabelStack.h" + +static const int default_timeline_width = 1000; +static const int default_timeline_height = 300; + +/** + * + */ +GtkStatsTimeline:: +GtkStatsTimeline(GtkStatsMonitor *monitor) : + PStatTimeline(monitor, 0, 0), + GtkStatsGraph(monitor, false) +{ + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + // Add a DrawingArea widget on top of the graph, to display all of the scale + // units. + _scale_area = gtk_drawing_area_new(); + g_signal_connect(G_OBJECT(_scale_area), "draw", + G_CALLBACK(scale_area_draw_callback), this); + gtk_box_pack_start(GTK_BOX(_graph_vbox), _scale_area, FALSE, FALSE, 0); + + // It should be large enough to display the labels. + { + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, "0123456789 ms"); + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + gtk_widget_set_size_request(_scale_area, 0, height + _pixel_scale / 2); + g_object_unref(layout); + } + + // Add a drawing area to the left of the graph to show the thread labels. + _thread_area = gtk_drawing_area_new(); + gtk_box_pack_start(GTK_BOX(_graph_hbox), _thread_area, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(_graph_hbox), _thread_area, 0); + g_signal_connect(G_OBJECT(_thread_area), "draw", + G_CALLBACK(thread_area_draw_callback), this); + + // Listen for mouse wheel and keyboard events. + gtk_widget_add_events(_graph_window, GDK_SMOOTH_SCROLL_MASK | + GDK_SCROLL_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + gtk_widget_set_can_focus(_graph_window, TRUE); + g_signal_connect(G_OBJECT(_graph_window), "scroll_event", + G_CALLBACK(scroll_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "key_press_event", + G_CALLBACK(key_press_callback), this); + g_signal_connect(G_OBJECT(_graph_window), "key_release_event", + G_CALLBACK(key_release_callback), this); + + // Set up trackpad pinch and swipe gestures. + _zoom_gesture = gtk_gesture_zoom_new(_graph_window); + g_signal_connect(_zoom_gesture, "begin", + G_CALLBACK(+[](GtkGestureZoom *gesture, GdkEventSequence *sequence, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + self->_zoom_scale = self->get_horizontal_scale(); + }), this); + + g_signal_connect(_zoom_gesture, "scale-changed", + G_CALLBACK((+[](GtkGestureZoom *gesture, gdouble scale, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + gdouble x, y; + if (gtk_gesture_get_point(GTK_GESTURE(gesture), NULL, &x, &y)) { + int graph_x = (int)(x * self->_cr_scale); + self->zoom_by(log(scale) * 0.8, self->pixel_to_timestamp(graph_x)); + self->start_animation(); + } + })), this); + + int min_height = 0; + if (!_threads.empty()) { + double height = row_to_pixel(get_num_rows()) + _pixel_scale * 2.5; + min_height = height / _cr_scale; + + // Never make the window taller than the screen. + GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(_window)); + min_height = std::min(min_height, gdk_screen_get_height(screen)); + } + + gtk_widget_set_size_request(_graph_window, + default_timeline_width * monitor->get_resolution() / 96, + std::max(min_height, (int)(default_timeline_height * monitor->get_resolution() / 96))); + + gtk_window_set_title(GTK_WINDOW(_window), "Timeline"); + + _grid_pattern = cairo_pattern_create_rgb(0xdd / 255.0, 0xdd / 255.0, 0xdd / 255.0); + + gtk_widget_show_all(_window); + gtk_widget_show(_window); + + // Allow the window to be resized as small as the user likes. We have to do + // this after the window has been shown; otherwise, it will affect the + // window's initial size. + gtk_widget_set_size_request(_graph_window, 0, 0); + + clear_region(); +} + +/** + * + */ +GtkStatsTimeline:: +~GtkStatsTimeline() { + cairo_pattern_destroy(_grid_pattern); + g_object_unref(_zoom_gesture); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void GtkStatsTimeline:: +new_data(int thread_index, int frame_number) { + PStatTimeline::new_data(thread_index, frame_number); +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void GtkStatsTimeline:: +force_redraw() { + assert(_cr); + if (_cr) { + PStatTimeline::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void GtkStatsTimeline:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatTimeline::changed_size(graph_xsize, graph_ysize); +} + +/** + * Erases the chart area. + */ +void GtkStatsTimeline:: +clear_region() { + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_paint(_cr); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void GtkStatsTimeline:: +begin_draw() { +} + +/** + * Draws a horizontal separator. + */ +void GtkStatsTimeline:: +draw_separator(int row) { + cairo_set_source(_cr, _grid_pattern); + cairo_rectangle(_cr, 0, (row_to_pixel(row) + row_to_pixel(row + 1)) / 2.0, + get_xsize(), _pixel_scale * 1 / 3); + cairo_fill(_cr); +} + +/** + * Draws a vertical guide bar. If the row is -1, draws it in all rows. + */ +void GtkStatsTimeline:: +draw_guide_bar(int x, GuideBarStyle style) { + double width = _pixel_scale / 3.0; + if (style == GBS_frame) { + width *= 2; + } + + cairo_set_source(_cr, _grid_pattern); + cairo_rectangle(_cr, x - width / 2.0, 0, width, get_ysize()); + cairo_fill(_cr); +} + +/** + * Draws a single bar in the chart for the indicated row, in the color for the + * given collector, for the indicated horizontal pixel range. + */ +void GtkStatsTimeline:: +draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name) { + int top = row_to_pixel(row); + int bottom = row_to_pixel(row + 1); + int scale = _pixel_scale; + + bool is_highlighted = row == _highlighted_row && _highlighted_x >= from_x && _highlighted_x < to_x; + cairo_set_source(_cr, get_collector_pattern(collector_index, is_highlighted)); + + if (to_x < from_x + 1) { + // Too tiny to draw. + } + else if (to_x < from_x + scale) { + // It's just a tiny sliver. This is a more reliable way to draw it. + cairo_rectangle(_cr, from_x, top, to_x - from_x, bottom - top); + cairo_fill(_cr); + } + else { + int left = std::max(from_x, -scale - 1); + int right = std::min(std::max(to_x, from_x + 1), get_xsize() + scale); + + double radius = std::min((double)scale, (right - left) / 2.0); + cairo_new_sub_path(_cr); + cairo_arc(_cr, right - radius, top + radius, radius, -0.5 * M_PI, 0.0); + cairo_arc(_cr, right - radius, bottom - radius, radius, 0.0, 0.5 * M_PI); + cairo_arc(_cr, left + radius, bottom - radius, radius, 0.5 * M_PI, M_PI); + cairo_arc(_cr, left + radius, top + radius, radius, M_PI, 1.5 * M_PI); + cairo_close_path(_cr); + cairo_fill(_cr); + + if ((to_x - from_x) >= scale * 4) { + // Only bother drawing the text if we've got some space to draw on. + // Choose a suitable foreground color. + LRGBColor fg = get_collector_text_color(collector_index, is_highlighted); + cairo_set_source_rgb(_cr, fg[0], fg[1], fg[2]); + + // Make sure that the text doesn't run off the chart. + int text_width, text_height; + PangoLayout *layout = gtk_widget_create_pango_layout(_graph_window, collector_name.c_str()); + pango_layout_set_attributes(layout, _pango_attrs); + pango_layout_set_height(layout, -1); + pango_layout_get_pixel_size(layout, &text_width, &text_height); + + double center = (from_x + to_x) / 2.0; + double text_left = std::max(from_x, 0) + scale / 2.0; + double text_right = std::min(to_x, get_xsize()) - scale / 2.0; + double text_top = top + (bottom - top - text_height) / 2.0; + + if (text_width >= text_right - text_left) { + size_t c = collector_name.rfind(':'); + if (text_right - text_left < scale * 6) { + // It's a really tiny space. Draw a single letter. + const char *ch = collector_name.data() + (c != std::string::npos ? c + 1 : 0); + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + pango_layout_set_text(layout, ch, 1); + } else { + // Maybe just use everything after the last colon. + if (c != std::string::npos) { + pango_layout_set_text(layout, collector_name.data() + c + 1, + collector_name.size() - c - 1); + pango_layout_get_pixel_size(layout, &text_width, &text_height); + } + } + } + + if (text_width >= text_right - text_left) { + // It's going to be tricky to fit it, let pango figure it out. + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_width(layout, (text_right - text_left) * PANGO_SCALE); + cairo_move_to(_cr, text_left, text_top); + } + else if (center - text_width / 2.0 < 0.0) { + // Put it against the left-most edge. + cairo_move_to(_cr, scale, text_top); + } + else if (center + text_width / 2.0 >= get_xsize()) { + // Put it against the right-most edge. + cairo_move_to(_cr, get_xsize() - scale - text_width, text_top); + } + else { + // It fits just fine, center it. + cairo_move_to(_cr, center - text_width / 2.0, text_top); + } + + pango_cairo_show_layout(_cr, layout); + g_object_unref(layout); + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void GtkStatsTimeline:: +end_draw() { + gtk_widget_queue_draw(_graph_window); + + if (_threads_changed) { + // Calculate the size of the thread area. + PangoLayout *layout = gtk_widget_create_pango_layout(_thread_area, ""); + + int max_width = 0; + for (const ThreadRow &thread_row : _threads) { + if (!thread_row._visible) { + continue; + } + pango_layout_set_text(layout, thread_row._label.c_str(), thread_row._label.size()); + + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + if (width > max_width) { + max_width = width; + } + } + + gtk_widget_set_size_request(_thread_area, max_width + _pixel_scale * 2, 0); + g_object_unref(layout); + gtk_widget_queue_draw(_thread_area); + _threads_changed = false; + } + + if (_guide_bars_changed) { + gtk_widget_queue_draw(_scale_area); + _guide_bars_changed = false; + } +} + +/** + * Called at the end of the draw cycle. + */ +void GtkStatsTimeline:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool GtkStatsTimeline:: +animate(double time, double dt) { + return PStatTimeline::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool GtkStatsTimeline:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + GtkStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void GtkStatsTimeline:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + GtkStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * This is called during the servicing of the draw event; it gives a derived + * class opportunity to do some further painting into the graph window. + */ +void GtkStatsTimeline:: +additional_graph_window_paint(cairo_t *cr) { +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string GtkStatsTimeline:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return PStatTimeline::get_bar_tooltip(pixel_to_row(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +GtkStatsGraph::DragMode GtkStatsTimeline:: +consider_drag_start(int graph_x, int graph_y) { + return GtkStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +gboolean GtkStatsTimeline:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + if (button == 3) { + // Right-clicking a color bar brings up a context menu. + int row = pixel_to_row(graph_y); + + ColorBar bar; + if (find_bar(row, graph_x, bar)) { + GtkWidget *menu = gtk_menu_new(); + _popup_bar = bar; + + std::string label = get_bar_tooltip(row, graph_x); + if (!label.empty()) { + GtkWidget *menu_item = gtk_menu_item_new_with_label(label.c_str()); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + gtk_widget_set_sensitive(menu_item, FALSE); + } + + { + GtkWidget *menu_item = gtk_menu_item_new_with_label("Zoom To"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(+[] (GtkWidget *widget, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + const ColorBar &bar = self->_popup_bar; + double width = bar._end - bar._start; + self->zoom_to(width * 1.5, (bar._end + bar._start) / 2.0); + self->scroll_to(bar._start - width / 4.0); + self->start_animation(); + }), + this); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_strip_chart, + bar._thread_index, bar._collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Strip Chart"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_flame_graph, + bar._thread_index, bar._collector_index, bar._frame_number, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Flame Graph"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_piano_roll, bar._thread_index, -1, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Open Piano Roll"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + GtkWidget *menu_item = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_choose_color, -1, bar._collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Change Color..."); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + { + const GtkStatsMonitor::MenuDef *menu_def = GtkStatsGraph::_monitor->add_menu({ + GtkStatsMonitor::CT_reset_color, -1, bar._collector_index, + }); + + GtkWidget *menu_item = gtk_menu_item_new_with_label("Reset Color"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + g_signal_connect(G_OBJECT(menu_item), "activate", + G_CALLBACK(GtkStatsMonitor::menu_activate), + (void *)menu_def); + } + + gtk_widget_show_all(menu); + gtk_menu_popup_at_pointer(GTK_MENU(menu), nullptr); + return TRUE; + } + return FALSE; + } + else if (double_click && button == 1) { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + int row = pixel_to_row(graph_y); + ColorBar bar; + if (find_bar(row, graph_x, bar)) { + double width = bar._end - bar._start; + zoom_to(width * 1.5, pixel_to_timestamp(graph_x)); + scroll_to(bar._start - width / 4.0); + } else { + // Double-clicking the white area zooms out. + _zoom_speed -= 100.0; + } + start_animation(); + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_pan); + _drag_start_x = graph_x; + _scroll_speed = 0.0; + _zoom_center = pixel_to_timestamp(graph_x); + return TRUE; + } + } + + return GtkStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +gboolean GtkStatsTimeline:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return GtkStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +gboolean GtkStatsTimeline:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int row = pixel_to_row(graph_y); + std::swap(_highlighted_x, graph_x); + std::swap(_highlighted_row, row); + + if (row >= 0) { + PStatTimeline::force_redraw(row, graph_x, graph_x); + } + PStatTimeline::force_redraw(_highlighted_row, _highlighted_x, _highlighted_x); + + if ((_keys_held & (F_w | F_s)) != 0) { + // Update the zoom center if we move the mouse while zooming with the + // keyboard. + _zoom_center = pixel_to_timestamp(graph_x); + } + } + else { + // If the mouse is in some drag mode, stop highlighting. + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + } + } + + if (_drag_mode == DM_pan) { + int delta = _drag_start_x - graph_x; + _drag_start_x = graph_x; + set_horizontal_scroll(get_horizontal_scroll() + pixel_to_height(delta)); + return 0; + } + + return GtkStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +gboolean GtkStatsTimeline:: +handle_leave() { + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + } + return TRUE; +} + +/** + * + */ +gboolean GtkStatsTimeline:: +handle_scroll(int graph_x, int graph_y, double dx, double dy, bool ctrl_held) { + gboolean handled = FALSE; + + if (dy != 0.0) { + handled = TRUE; + if (ctrl_held) { + zoom_by(dy, pixel_to_timestamp(graph_x)); + start_animation(); + } else { + double delta = (int)(dy * _pixel_scale * 5 + 0.5); + int new_scroll = _scroll - delta; + if (_threads.empty()) { + new_scroll = 0; + } else { + new_scroll = (std::min)(new_scroll, get_num_rows() * _pixel_scale * 5 + _pixel_scale * 2 - get_ysize()); + new_scroll = (std::max)(new_scroll, 0); + } + delta = new_scroll - _scroll; + if (delta != 0) { + _scroll = new_scroll; + gtk_widget_queue_draw(_thread_area); + force_redraw(); + } + } + } + + if (dx != 0.0) { + _scroll_speed += dx * 10.0; + handled = TRUE; + start_animation(); + } + + return handled; +} + +/** + * + */ +gboolean GtkStatsTimeline:: +handle_zoom(int graph_x, int graph_y, double scale) { + zoom_to(get_horizontal_scale() / scale, pixel_to_timestamp(graph_x)); + start_animation(); + return TRUE; +} + +/** + * + */ +gboolean GtkStatsTimeline:: +handle_key(bool pressed, guint val, guint16 hw_code) { + // Accept WASD based on their position rather than their mapping + int flag = 0; + switch (hw_code) { + case 25: + flag = F_w; + break; + case 38: + flag = F_a; + break; + case 39: + flag = F_s; + break; + case 40: + flag = F_d; + break; + } + if (flag == 0) { + switch (val) { + case GDK_KEY_Left: + flag = F_left; + break; + case GDK_KEY_Right: + flag = F_right; + break; + case GDK_KEY_w: + flag = F_w; + break; + case GDK_KEY_a: + flag = F_a; + break; + case GDK_KEY_s: + flag = F_s; + break; + case GDK_KEY_d: + flag = F_d; + break; + } + } + if (flag != 0) { + if (pressed) { + if (flag & (F_w | F_s)) { + // Pfoo, GTK sure does make it hard to just get the cursor position. + GdkWindow *window = gtk_widget_get_window(_graph_window); + GdkDisplay *display = gdk_window_get_display(window); + GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); + GdkDevice *device = gdk_device_manager_get_client_pointer(device_manager); + gint x, y; + gdk_window_get_device_position(window, device, &x, &y, nullptr); + _zoom_center = pixel_to_timestamp(x * _cr_scale); + } + if (_keys_held == 0) { + start_animation(); + } + _keys_held |= flag; + } + else if (_keys_held != 0) { + _keys_held &= ~flag; + } + return TRUE; + } + return FALSE; +} + +/** + * This is called during the servicing of the draw event. + */ +void GtkStatsTimeline:: +draw_guide_labels(cairo_t *cr) { + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_label(cr, get_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void GtkStatsTimeline:: +draw_guide_label(cairo_t *cr, const PStatGraph::GuideBar &bar) { + const std::string &label = bar._label; + if (label.empty()) { + return; + } + + bool center = true; + switch (bar._style) { + case GBS_target: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_user: + cairo_set_source_rgb(cr, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2]); + break; + + case GBS_normal: + cairo_set_source_rgb(cr, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2]); + break; + + case GBS_frame: + cairo_set_source_rgb(cr, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2]); + center = false; + break; + } + + PangoLayout *layout = gtk_widget_create_pango_layout(_scale_area, label.c_str()); + + if (bar._style != GBS_frame) { + // Make the offsets slightly smaller. + PangoAttrList *attrs = pango_attr_list_new(); + PangoAttribute *attr = pango_attr_scale_new(0.9); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert(attrs, attr); + pango_layout_set_attributes(layout, attrs); + pango_attr_list_unref(attrs); + } + + int width, height; + pango_layout_get_pixel_size(layout, &width, &height); + + int x = timestamp_to_pixel(bar._height); + if (x >= 0 && x + width * _cr_scale < get_xsize()) { + // Now convert our x to a coordinate within our drawing area. + int junk_y; + + x /= _cr_scale; + + // The x coordinate comes from the graph_window. + gtk_widget_translate_coordinates(_graph_window, _scale_area, + x, 0, &x, &junk_y); + + GtkAllocation allocation; + gtk_widget_get_allocation(_scale_area, &allocation); + + if (x >= 0) { + int this_x = x; + if (center) { + this_x -= width / 2; + } + if (this_x + width < allocation.width) { + cairo_move_to(cr, this_x, allocation.height - height); + pango_cairo_show_layout(cr, layout); + } + } + } + + g_object_unref(layout); +} + +/** + * This is called during the servicing of the draw event. + */ +void GtkStatsTimeline:: +draw_thread_labels(cairo_t *cr) { + for (const ThreadRow &thread_row : _threads) { + if (thread_row._visible) { + draw_thread_label(cr, thread_row); + } + } +} + +/** + * Draws the text for the indicated thread on the side of the graph. + */ +void GtkStatsTimeline:: +draw_thread_label(cairo_t *cr, const ThreadRow &thread_row) { + int top = row_to_pixel(thread_row._row_offset); + if (top <= get_ysize()) { + // Now convert our y to a coordinate within our drawing area. + top /= _cr_scale; + + // The y coordinate comes from the graph_window. + int junk_x; + gtk_widget_translate_coordinates(_graph_window, _thread_area, + 0, top, &junk_x, &top); + + GtkAllocation allocation; + gtk_widget_get_allocation(_thread_area, &allocation); + + PangoLayout *layout = gtk_widget_create_pango_layout(_thread_area, thread_row._label.c_str()); + pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_width(layout, (allocation.width - _pixel_scale * 2) * PANGO_SCALE); + + cairo_move_to(cr, _pixel_scale, top); + pango_cairo_show_layout(cr, layout); + g_object_unref(layout); + } +} + +/** + * Draws in the scale labels. + */ +gboolean GtkStatsTimeline:: +scale_area_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + self->draw_guide_labels(cr); + + return TRUE; +} + +/** + * Draws in the thread labels. + */ +gboolean GtkStatsTimeline:: +thread_area_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + self->draw_thread_labels(cr); + + return TRUE; +} + +/** + * + */ +gboolean GtkStatsTimeline:: +scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + + bool ctrl_held = (event->state & GDK_CONTROL_MASK) != 0; + + double dx, dy; + if (event->direction == GDK_SCROLL_UP) { + dx = 0; + dy = 1; + } + else if (event->direction == GDK_SCROLL_DOWN) { + dx = 0; + dy = -1; + } + else if (event->direction == GDK_SCROLL_LEFT) { + dx = 1; + dy = 0; + } + else if (event->direction == GDK_SCROLL_RIGHT) { + dx = -1; + dy = 0; + } + else if (!gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) { + return FALSE; + } + + int graph_x = (int)(event->x * self->_cr_scale); + int graph_y = (int)(event->y * self->_cr_scale); + return self->handle_scroll(graph_x, graph_y, dx, dy, ctrl_held); +} + +/** + * + */ +gboolean GtkStatsTimeline:: +key_press_callback(GtkWidget *widget, GdkEventKey *event, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + return self->handle_key(true, event->keyval, event->hardware_keycode); +} + +/** + * + */ +gboolean GtkStatsTimeline:: +key_release_callback(GtkWidget *widget, GdkEventKey *event, gpointer data) { + GtkStatsTimeline *self = (GtkStatsTimeline *)data; + return self->handle_key(false, event->keyval, event->hardware_keycode); +} diff --git a/pandatool/src/gtk-stats/gtkStatsTimeline.h b/pandatool/src/gtk-stats/gtkStatsTimeline.h new file mode 100644 index 00000000..b354712d --- /dev/null +++ b/pandatool/src/gtk-stats/gtkStatsTimeline.h @@ -0,0 +1,101 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file gtkStatsTimeline.h + * @author rdb + * @date 2022-02-17 + */ + +#ifndef GTKSTATSTIMELINE_H +#define GTKSTATSTIMELINE_H + +#include "pandatoolbase.h" + +#include "gtkStatsGraph.h" +#include "pStatTimeline.h" + +class GtkStatsMonitor; + +/** + * A window that draws all of the start/stop event pairs on each thread on a + * horizontal scrolling timeline, with concurrent start/stop pairs stacked + * underneath each other. + */ +class GtkStatsTimeline final : public PStatTimeline, public GtkStatsGraph { +public: + GtkStatsTimeline(GtkStatsMonitor *monitor); + virtual ~GtkStatsTimeline(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + +protected: + virtual void clear_region(); + virtual void begin_draw(); + virtual void draw_separator(int row); + virtual void draw_guide_bar(int x, GuideBarStyle style); + virtual void draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual void additional_graph_window_paint(cairo_t *cr); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual gboolean handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual gboolean handle_button_release(int graph_x, int graph_y); + virtual gboolean handle_motion(int graph_x, int graph_y); + virtual gboolean handle_leave(); + gboolean handle_scroll(int graph_x, int graph_y, + double dx, double dy, bool ctrl_held); + gboolean handle_zoom(int graph_x, int graph_y, double scale); + gboolean handle_key(bool pressed, guint val, guint16 hw_code); + +private: + void draw_guide_labels(cairo_t *cr); + void draw_guide_label(cairo_t *cr, const GuideBar &bar); + void draw_thread_labels(cairo_t *cr); + void draw_thread_label(cairo_t *cr, const ThreadRow &thread_row); + + static gboolean scale_area_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data); + static gboolean thread_area_draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data); + static gboolean scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer data); + static gboolean key_press_callback(GtkWidget *widget, GdkEventKey *event, gpointer data); + static gboolean key_release_callback(GtkWidget *widget, GdkEventKey *event, gpointer data); + + int row_to_pixel(int y) const { + return y * _pixel_scale * 5 + _pixel_scale - _scroll; + } + int pixel_to_row(int y) const { + return (y + _scroll - _pixel_scale) / (_pixel_scale * 5); + } + + GtkWidget *_thread_area; + + cairo_pattern_t *_grid_pattern; + + int _highlighted_row = -1; + int _highlighted_x = 0; + int _scroll = 0; + ColorBar _popup_bar; + + double _zoom_scale = 1.0; + GtkGesture *_zoom_gesture = nullptr; +}; + +#endif diff --git a/pandatool/src/gtk-stats/gtkstats_composite1.cxx b/pandatool/src/gtk-stats/gtkstats_composite1.cxx new file mode 100644 index 00000000..3ab44d39 --- /dev/null +++ b/pandatool/src/gtk-stats/gtkstats_composite1.cxx @@ -0,0 +1,11 @@ +#include "gtkStats.cxx" +#include "gtkStatsChartMenu.cxx" +#include "gtkStatsFlameGraph.cxx" +#include "gtkStatsGraph.cxx" +#include "gtkStatsLabel.cxx" +#include "gtkStatsLabelStack.cxx" +#include "gtkStatsMonitor.cxx" +#include "gtkStatsPianoRoll.cxx" +#include "gtkStatsServer.cxx" +#include "gtkStatsStripChart.cxx" +#include "gtkStatsTimeline.cxx" diff --git a/pandatool/src/imagebase/CMakeLists.txt b/pandatool/src/imagebase/CMakeLists.txt new file mode 100644 index 00000000..46ffce3d --- /dev/null +++ b/pandatool/src/imagebase/CMakeLists.txt @@ -0,0 +1,20 @@ +set(P3IMAGEBASE_HEADERS + imageBase.h + imageFilter.h + imageReader.h + imageWriter.h imageWriter.I +) + +set(P3IMAGEBASE_SOURCES + imageBase.cxx + imageFilter.cxx + imageReader.cxx + imageWriter.cxx +) + +composite_sources(p3imagebase P3IMAGEBASE_SOURCES) +add_library(p3imagebase STATIC ${P3IMAGEBASE_HEADERS} ${P3IMAGEBASE_SOURCES}) +target_link_libraries(p3imagebase p3progbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/imagebase/imageBase.cxx b/pandatool/src/imagebase/imageBase.cxx new file mode 100644 index 00000000..837f0bdf --- /dev/null +++ b/pandatool/src/imagebase/imageBase.cxx @@ -0,0 +1,30 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageBase.cxx + * @author drose + * @date 2000-06-19 + */ + +#include "imageBase.h" + +/** + * + */ +ImageBase:: +ImageBase() { +} + + +/** + * + */ +bool ImageBase:: +post_command_line() { + return ProgramBase::post_command_line(); +} diff --git a/pandatool/src/imagebase/imageBase.h b/pandatool/src/imagebase/imageBase.h new file mode 100644 index 00000000..5b106c8c --- /dev/null +++ b/pandatool/src/imagebase/imageBase.h @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageBase.h + * @author drose + * @date 2000-06-19 + */ + +#ifndef IMAGEBASE_H +#define IMAGEBASE_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "coordinateSystem.h" +#include "pnmImage.h" + +/** + * This specialization of ProgramBase is intended for programs that read + * and/or write a single image file. (See ImageMultiBase for programs that + * operate on multiple image files at once.) + * + * This is just a base class; see ImageReader, ImageWriter, or ImageFilter + * according to your particular I/O needs. + */ +class ImageBase : public ProgramBase { +public: + ImageBase(); + +protected: + virtual bool post_command_line(); + +protected: + PNMImage _image; +}; + +#endif diff --git a/pandatool/src/imagebase/imageFilter.cxx b/pandatool/src/imagebase/imageFilter.cxx new file mode 100644 index 00000000..63957ed5 --- /dev/null +++ b/pandatool/src/imagebase/imageFilter.cxx @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFilter.cxx + * @author drose + * @date 2000-06-19 + */ + +#include "imageFilter.h" + +/** + * + */ +ImageFilter:: +ImageFilter(bool allow_last_param) : + ImageWriter(allow_last_param) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] inputimage outputimage"); + } + add_runline("[opts] -o outputimage inputimage"); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool ImageFilter:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + return ImageReader::handle_args(args); +} diff --git a/pandatool/src/imagebase/imageFilter.h b/pandatool/src/imagebase/imageFilter.h new file mode 100644 index 00000000..ae53da53 --- /dev/null +++ b/pandatool/src/imagebase/imageFilter.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFilter.h + * @author drose + * @date 2000-06-19 + */ + +#ifndef IMAGEFILTER_H +#define IMAGEFILTER_H + +#include "pandatoolbase.h" + +#include "imageReader.h" +#include "imageWriter.h" + +/** + * This is the base class for a program that reads an image file, operates on + * it, and writes another image file out. + */ +class ImageFilter : public ImageReader, public ImageWriter { +public: + ImageFilter(bool allow_last_param); + +protected: + virtual bool handle_args(Args &args); +}; + +#endif diff --git a/pandatool/src/imagebase/imageReader.cxx b/pandatool/src/imagebase/imageReader.cxx new file mode 100644 index 00000000..4e4e6690 --- /dev/null +++ b/pandatool/src/imagebase/imageReader.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageReader.cxx + * @author drose + * @date 2000-06-19 + */ + +#include "imageReader.h" + +/** + * + */ +ImageReader:: +ImageReader() { + clear_runlines(); + add_runline("[opts] imagename"); +} + +/** + * + */ +bool ImageReader:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the image file to read on the command line.\n"; + return false; + } + + if (args.size() > 1) { + nout << "Specify only one image on the command line.\n"; + return false; + } + + if (!_image.read(args[0])) { + nout << "Unable to read image file " << args[0] << ".\n"; + exit(1); + } + + return true; +} diff --git a/pandatool/src/imagebase/imageReader.h b/pandatool/src/imagebase/imageReader.h new file mode 100644 index 00000000..c3c81856 --- /dev/null +++ b/pandatool/src/imagebase/imageReader.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageReader.h + * @author drose + * @date 2000-06-19 + */ + +#ifndef IMAGEREADER_H +#define IMAGEREADER_H + +#include "pandatoolbase.h" + +#include "imageBase.h" + +/** + * This is the base class for a program that reads an image file, but doesn't + * write an image file. + */ +class ImageReader : virtual public ImageBase { +public: + ImageReader(); + +protected: + virtual bool handle_args(Args &args); + +}; + +#endif diff --git a/pandatool/src/imagebase/imageWriter.I b/pandatool/src/imagebase/imageWriter.I new file mode 100644 index 00000000..b8400819 --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.I @@ -0,0 +1,20 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageWriter.I + * @author drose + * @date 2000-06-19 + */ + +/** + * Writes the generated to the user's specified output filename. + */ +INLINE void ImageWriter:: +write_image() { + write_image(_image); +} diff --git a/pandatool/src/imagebase/imageWriter.cxx b/pandatool/src/imagebase/imageWriter.cxx new file mode 100644 index 00000000..eb50188e --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageWriter.cxx + * @author drose + * @date 2000-06-19 + */ + +#include "imageWriter.h" + +/** + * Image-writing type programs *must* specify their output file using -o. + */ +ImageWriter:: +ImageWriter(bool allow_last_param) : + WithOutputFile(allow_last_param, false, true) +{ + clear_runlines(); + if (_allow_last_param) { + add_runline("[opts] outputimage"); + } + add_runline("[opts] -o outputimage"); + + std::string o_description; + if (_allow_last_param) { + o_description = + "Specify the filename to which the resulting image file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file."; + } else { + o_description = + "Specify the filename to which the resulting image file will be written."; + } + + add_option + ("o", "filename", 50, o_description, + &ImageWriter::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +/** + * Writes the generated to the user's specified output filename. + */ +void ImageWriter:: +write_image(const PNMImage &image) { + if (!image.write(get_output_filename())) { + nout << "Unable to write output image to " + << get_output_filename() << "\n"; + exit(1); + } +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool ImageWriter:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 0)) { + return false; + } + + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + nout << (*ai) << " "; + } + nout << "\r"; + return false; + } + + return true; +} diff --git a/pandatool/src/imagebase/imageWriter.h b/pandatool/src/imagebase/imageWriter.h new file mode 100644 index 00000000..f902af29 --- /dev/null +++ b/pandatool/src/imagebase/imageWriter.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageWriter.h + * @author drose + * @date 2000-06-19 + */ + +#ifndef IMAGEWRITER_H +#define IMAGEWRITER_H + +#include "pandatoolbase.h" +#include "imageBase.h" +#include "withOutputFile.h" + +#include "filename.h" + +/** + * This is the base class for a program that generates an image file output, + * but doesn't read any for input. + */ +class ImageWriter : virtual public ImageBase, public WithOutputFile { +public: + ImageWriter(bool allow_last_param); + + INLINE void write_image(); + void write_image(const PNMImage &image); + +protected: + virtual bool handle_args(Args &args); +}; + +#include "imageWriter.I" + +#endif diff --git a/pandatool/src/imagebase/p3imagebase_composite1.cxx b/pandatool/src/imagebase/p3imagebase_composite1.cxx new file mode 100644 index 00000000..35b20753 --- /dev/null +++ b/pandatool/src/imagebase/p3imagebase_composite1.cxx @@ -0,0 +1,6 @@ + +#include "imageBase.cxx" +#include "imageFilter.cxx" +#include "imageReader.cxx" +#include "imageWriter.cxx" + diff --git a/pandatool/src/imageprogs/CMakeLists.txt b/pandatool/src/imageprogs/CMakeLists.txt new file mode 100644 index 00000000..e91d4c69 --- /dev/null +++ b/pandatool/src/imageprogs/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(image-fix-hidden-color imageFixHiddenColor.cxx imageFixHiddenColor.h) +target_link_libraries(image-fix-hidden-color p3imagebase) +install(TARGETS image-fix-hidden-color EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(image-info imageInfo.cxx imageInfo.h) +target_link_libraries(image-info p3imagebase) +install(TARGETS image-info EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(image-resize imageResize.cxx imageResize.h) +target_link_libraries(image-resize p3imagebase) +install(TARGETS image-resize EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(image-transform-colors imageTransformColors.cxx imageTransformColors.h) +target_link_libraries(image-transform-colors p3imagebase) +install(TARGETS image-transform-colors EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(image-trans imageTrans.cxx imageTrans.h) +target_link_libraries(image-trans p3imagebase) +install(TARGETS image-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/imageprogs/imageFixHiddenColor.I b/pandatool/src/imageprogs/imageFixHiddenColor.I new file mode 100644 index 00000000..8c41f570 --- /dev/null +++ b/pandatool/src/imageprogs/imageFixHiddenColor.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFixHiddenColor.I + * @author drose + * @date 2003-03-13 + */ diff --git a/pandatool/src/imageprogs/imageFixHiddenColor.cxx b/pandatool/src/imageprogs/imageFixHiddenColor.cxx new file mode 100644 index 00000000..44d1c91a --- /dev/null +++ b/pandatool/src/imageprogs/imageFixHiddenColor.cxx @@ -0,0 +1,149 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFixHiddenColor.cxx + * @author drose + * @date 2003-03-13 + */ + +#include "imageFixHiddenColor.h" +#include "string_utils.h" + +/** + * + */ +ImageFixHiddenColor:: +ImageFixHiddenColor() : ImageFilter(true) { + set_program_brief("change the color of transparent pixels in an image"); + set_program_description + ("This program is designed to fix the color channels of an " + "alpha-cutout image, making the \"color\" of the invisible part of the " + "image consistent with the rest of the image. It does this by " + "analyzing the RGB values of the image where alpha == 1, and using the " + "average of these values as the overall image color, which it then " + "applies to the image wherever alpha == 0. When the alpha value is " + "neither 1 nor 0, the pixel is ignored.\n\n" + + "This process is important for applications that filter the texture " + "size down by averaging neighboring pixels. If the color under the " + "alpha == 0 pixels is very different from the color elsewhere, this " + "kind of filtering can have a visible effect on the image's color, even " + "where alpha != 0."); + + add_option + ("alpha", "filename", 0, + "Specifies a separate filename that will be used in lieu of the " + "alpha channel on the source image. If this file has an alpha " + "channel, that alpha channel is used; otherwise, the grayscale " + "value of the image is used.", + &ImageFixHiddenColor::dispatch_filename, nullptr, &_alpha_filename); + + add_option + ("opaque", "alpha", 0, + "Specifies the minimum alpha value (in the range of 0 to 1) for a " + "pixel to be considered fully opaque. The default is 1.", + &ImageFixHiddenColor::dispatch_double, nullptr, &_min_opaque_alpha); + + add_option + ("transparent", "alpha", 0, + "Specifies the maximum alpha value (in the range of 0 to 1) for a " + "pixel to be considered fully transparent. The default is 0.", + &ImageFixHiddenColor::dispatch_double, nullptr, &_max_transparent_alpha); + + _min_opaque_alpha = 1.0; + _max_transparent_alpha = 0.0; +} + +/** + * + */ +void ImageFixHiddenColor:: +run() { + PNMImage alpha_image; + + if (_alpha_filename.empty()) { + // No separate alpha file is provided; use the base file's alpha channel. + if (!_image.has_alpha()) { + nout << "Image does not have an alpha channel.\n"; + exit(1); + } + alpha_image = _image; + + } else { + // In this case, the alpha channel is in a separate file. + if (!alpha_image.read(_alpha_filename)) { + nout << "Unable to read " << _alpha_filename << ".\n"; + exit(1); + } + + if (!alpha_image.has_alpha()) { + // Copy the grayscale value to the alpha channel for the benefit of the + // code below. + alpha_image.add_alpha(); + int xi, yi; + for (yi = 0; yi < alpha_image.get_y_size(); ++yi) { + for (xi = 0; xi < alpha_image.get_x_size(); ++xi) { + alpha_image.set_alpha(xi, yi, alpha_image.get_gray(xi, yi)); + } + } + } + + // Make sure the alpha image matches the size of the source image. + if (alpha_image.get_x_size() != _image.get_x_size() || + alpha_image.get_y_size() != _image.get_y_size()) { + PNMImage scaled(_image.get_x_size(), _image.get_y_size(), alpha_image.get_num_channels()); + scaled.quick_filter_from(alpha_image); + alpha_image = scaled; + } + } + + // First, get the average color of all the opaque pixels. + int count = 0; + LRGBColorf color(0.0, 0.0, 0.0); + int xi, yi; + for (yi = 0; yi < _image.get_y_size(); ++yi) { + for (xi = 0; xi < _image.get_x_size(); ++xi) { + if (alpha_image.get_alpha(xi, yi) >= _min_opaque_alpha) { + color += _image.get_xel(xi, yi); + ++count; + } + } + } + if (count == 0) { + nout << "Image has no opaque pixels.\n"; + exit(1); + } + color /= (double)count; + nout << " average color of " << count << " opaque pixels is " << color << "\n"; + + // Now, apply that wherever there are transparent pixels. + count = 0; + for (yi = 0; yi < _image.get_y_size(); ++yi) { + for (xi = 0; xi < _image.get_x_size(); ++xi) { + if (alpha_image.get_alpha(xi, yi) <= _max_transparent_alpha) { + _image.set_xel(xi, yi, color); + ++count; + } + } + } + if (count == 0) { + nout << "Image has no transparent pixels.\n"; + exit(1); + } + nout << " applied to " << count << " transparent pixels.\n"; + + write_image(_image); +} + + +int main(int argc, char *argv[]) { + ImageFixHiddenColor prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageFixHiddenColor.h b/pandatool/src/imageprogs/imageFixHiddenColor.h new file mode 100644 index 00000000..eb272eb0 --- /dev/null +++ b/pandatool/src/imageprogs/imageFixHiddenColor.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFixHiddenColor.h + * @author drose + * @date 2003-03-13 + */ + +#ifndef IMAGEFIXHIDDENCOLOR_H +#define IMAGEFIXHIDDENCOLOR_H + +#include "pandatoolbase.h" + +#include "imageFilter.h" + +/** + * This program repairs an image's RGB values hidden behind an A value of 0. + */ +class ImageFixHiddenColor : public ImageFilter { +public: + ImageFixHiddenColor(); + + void run(); + +private: + Filename _alpha_filename; + double _min_opaque_alpha; + double _max_transparent_alpha; +}; + +#include "imageFixHiddenColor.I" + +#endif diff --git a/pandatool/src/imageprogs/imageInfo.cxx b/pandatool/src/imageprogs/imageInfo.cxx new file mode 100644 index 00000000..7cbde069 --- /dev/null +++ b/pandatool/src/imageprogs/imageInfo.cxx @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageInfo.cxx + * @author drose + * @date 2003-03-13 + */ + +#include "imageInfo.h" +#include "pnmImageHeader.h" + +/** + * + */ +ImageInfo:: +ImageInfo() { + set_program_brief("report the size of image files"); + set_program_description + ("This program reads the headers of a series of one or more " + "image files and reports the image sizes to standard output."); + + add_option + ("2", "", 0, + "Report only images that have a non-power-of-two size in either " + "dimension. Images whose dimensions are both a power of two will " + "not be mentioned.", + &ImageInfo::dispatch_none, &_report_power_2, nullptr); +} + +/** + * + */ +void ImageInfo:: +run() { + Args::const_iterator ai; + for (ai = _filenames.begin(); ai != _filenames.end(); ++ai) { + Filename filename = (*ai); + PNMImageHeader header; + if (!header.read_header(filename)) { + // Could not read the image header. + if (filename.exists()) { + nout << filename << ": could not read image.\n"; + } else { + nout << filename << ": does not exist.\n"; + } + } else { + // Successfully read the image header. + if (!_report_power_2 || + !is_power_2(header.get_x_size()) || + !is_power_2(header.get_y_size())) { + nout << filename << ": " << header.get_x_size() << " x " + << header.get_y_size() << " x " << header.get_num_channels() + << " (maxval = " << header.get_maxval() << ")\n"; + } + } + } +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool ImageInfo:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "List one or more image filenames on command line.\n"; + return false; + } + _filenames = args; + + return true; +} + +/** + * Returns true if the indicated value is a power of 2, false otherwise. + */ +bool ImageInfo:: +is_power_2(int value) const { + return (value & (value - 1)) == 0; +} + + +int main(int argc, char *argv[]) { + ImageInfo prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageInfo.h b/pandatool/src/imageprogs/imageInfo.h new file mode 100644 index 00000000..4a6e2e9e --- /dev/null +++ b/pandatool/src/imageprogs/imageInfo.h @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageInfo.h + * @author drose + * @date 2003-03-13 + */ + +#ifndef IMAGEINFO_H +#define IMAGEINFO_H + +#include "pandatoolbase.h" + +#include "programBase.h" + +/** + * This program reads the headers of a series of one or more images and + * reports their sizes to standard output. + */ +class ImageInfo : public ProgramBase { +public: + ImageInfo(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + bool is_power_2(int value) const; + + Args _filenames; + bool _report_power_2; +}; + +#endif diff --git a/pandatool/src/imageprogs/imageResize.I b/pandatool/src/imageprogs/imageResize.I new file mode 100644 index 00000000..25dd5f87 --- /dev/null +++ b/pandatool/src/imageprogs/imageResize.I @@ -0,0 +1,99 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageResize.I + * @author drose + * @date 2003-03-13 + */ + +/** + * + */ +INLINE ImageResize::SizeRequest:: +SizeRequest() { + _type = RT_none; +} + +/** + * Returns the type of the size request, or RT_none if the request has not + * been specified. + */ +INLINE ImageResize::RequestType ImageResize::SizeRequest:: +get_type() const { + return _type; +} + +/** + * Sets the size request to store an explicit pixel size. + */ +INLINE void ImageResize::SizeRequest:: +set_pixel_size(int pixel_size) { + _type = RT_pixel_size; + _e._pixel_size = pixel_size; +} + +/** + * Returns the explicit pixel size stored within the size request. + */ +INLINE int ImageResize::SizeRequest:: +get_pixel_size() const { + nassertr(_type == RT_pixel_size, 0); + return _e._pixel_size; +} + +/** + * Returns the explicit pixel size stored within the size request, or if a + * ratio has been stored, returns the computed pixel size based on the + * original size. + */ +INLINE int ImageResize::SizeRequest:: +get_pixel_size(int orig_pixel_size) const { + switch (_type) { + case RT_pixel_size: + return _e._pixel_size; + case RT_ratio: + return (int)(_e._ratio * orig_pixel_size + 0.5); + default: + return orig_pixel_size; + } +} + +/** + * Sets the size request to store a specific ratio. + */ +INLINE void ImageResize::SizeRequest:: +set_ratio(double ratio) { + _type = RT_ratio; + _e._ratio = ratio; +} + +/** + * Returns the specific ratio stored within the size request. + */ +INLINE double ImageResize::SizeRequest:: +get_ratio() const { + nassertr(_type == RT_ratio, 0); + return _e._ratio; +} + +/** + * Returns the specific ratio stored within the size request, or if a pixel + * size has been stored, returns the computed ratio based on the original + * size. + */ +INLINE double ImageResize::SizeRequest:: +get_ratio(int orig_pixel_size) const { + switch (_type) { + case RT_ratio: + return _e._ratio; + case RT_pixel_size: + return (double)_e._pixel_size / (double)orig_pixel_size; + default: + return 1.0; + } +} diff --git a/pandatool/src/imageprogs/imageResize.cxx b/pandatool/src/imageprogs/imageResize.cxx new file mode 100644 index 00000000..a41ff771 --- /dev/null +++ b/pandatool/src/imageprogs/imageResize.cxx @@ -0,0 +1,123 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageResize.cxx + * @author drose + * @date 2003-03-13 + */ + +#include "imageResize.h" +#include "string_utils.h" + +/** + * + */ +ImageResize:: +ImageResize() : ImageFilter(true) { + set_program_brief("resize an image file"); + set_program_description + ("This program reads an image file and resizes it to a larger or smaller " + "image file."); + + add_option + ("x", "xsize", 0, + "Specify the width of the output image in pixels, or as a percentage " + "of the original width (if a trailing percent sign is included). " + "If this is omitted, the ratio is taken from the ysize parameter.", + &ImageResize::dispatch_size_request, nullptr, &_x_size); + + add_option + ("y", "ysize", 0, + "Specify the height of the output image in pixels, or as a percentage " + "of the original height (if a trailing percent sign is included). " + "If this is omitted, the ratio is taken from the xsize parameter.", + &ImageResize::dispatch_size_request, nullptr, &_y_size); + + add_option + ("g", "radius", 0, + "Use Gaussian filtering to resize the image, with the indicated radius.", + &ImageResize::dispatch_double, &_use_gaussian_filter, &_filter_radius); + + add_option + ("1", "", 0, + "This option is ignored. It is provided only for backward compatibility " + "with a previous version of image-resize.", + &ImageResize::dispatch_none, nullptr, nullptr); + + _filter_radius = 1.0; +} + +/** + * + */ +void ImageResize:: +run() { + if (_x_size.get_type() == RT_none && _y_size.get_type() == RT_none) { + _x_size.set_ratio(1.0); + _y_size.set_ratio(1.0); + } else if (_x_size.get_type() == RT_none) { + _x_size.set_ratio(_y_size.get_ratio(_image.get_y_size())); + } else if (_y_size.get_type() == RT_none) { + _y_size.set_ratio(_x_size.get_ratio(_image.get_x_size())); + } + + int x_size = _x_size.get_pixel_size(_image.get_x_size()); + int y_size = _y_size.get_pixel_size(_image.get_y_size()); + + nout << "Resizing to " << x_size << " x " << y_size << "\n"; + PNMImage new_image(x_size, y_size, + _image.get_num_channels(), + _image.get_maxval(), _image.get_type()); + + if (_use_gaussian_filter) { + new_image.gaussian_filter_from(_filter_radius, _image); + } else { + new_image.quick_filter_from(_image); + } + + write_image(new_image); +} + +/** + * Interprets the -x or -y parameters. + */ +bool ImageResize:: +dispatch_size_request(const std::string &opt, const std::string &arg, void *var) { + SizeRequest *ip = (SizeRequest *)var; + if (!arg.empty() && arg[arg.length() - 1] == '%') { + // A ratio. + std::string str = arg.substr(0, arg.length() - 1); + double ratio; + if (!string_to_double(str, ratio)) { + nout << "Invalid ratio for -" << opt << ": " + << str << "\n"; + return false; + } + ip->set_ratio(ratio / 100.0); + + } else { + // A pixel size. + int pixel_size; + if (!string_to_int(arg, pixel_size)) { + nout << "Invalid pixel size for -" << opt << ": " + << arg << "\n"; + return false; + } + ip->set_pixel_size(pixel_size); + } + + return true; +} + + +int main(int argc, char *argv[]) { + ImageResize prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageResize.h b/pandatool/src/imageprogs/imageResize.h new file mode 100644 index 00000000..731786a4 --- /dev/null +++ b/pandatool/src/imageprogs/imageResize.h @@ -0,0 +1,68 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageResize.h + * @author drose + * @date 2003-03-13 + */ + +#ifndef IMAGERESIZE_H +#define IMAGERESIZE_H + +#include "pandatoolbase.h" + +#include "imageFilter.h" + +/** + * A program to read an image file and resize it to a larger or smaller image + * file. + */ +class ImageResize : public ImageFilter { +public: + ImageResize(); + + void run(); + +private: + static bool dispatch_size_request(const std::string &opt, const std::string &arg, void *var); + + enum RequestType { + RT_none, + RT_pixel_size, + RT_ratio, + }; + class SizeRequest { + public: + INLINE SizeRequest(); + INLINE RequestType get_type() const; + + INLINE void set_pixel_size(int pixel_size); + INLINE int get_pixel_size() const; + INLINE int get_pixel_size(int orig_pixel_size) const; + INLINE void set_ratio(double ratio); + INLINE double get_ratio() const; + INLINE double get_ratio(int orig_pixel_size) const; + + private: + RequestType _type; + union { + int _pixel_size; + double _ratio; + } _e; + }; + + SizeRequest _x_size; + SizeRequest _y_size; + + bool _use_gaussian_filter; + double _filter_radius; +}; + +#include "imageResize.I" + +#endif diff --git a/pandatool/src/imageprogs/imageTrans.cxx b/pandatool/src/imageprogs/imageTrans.cxx new file mode 100644 index 00000000..517abee6 --- /dev/null +++ b/pandatool/src/imageprogs/imageTrans.cxx @@ -0,0 +1,209 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageTrans.cxx + * @author drose + * @date 2000-06-19 + */ + +#include "imageTrans.h" +#include "string_utils.h" + +/** + * + */ +ImageTrans:: +ImageTrans() : ImageFilter(true) { + set_program_brief("apply transformations to an image file"); + set_program_description + ("This program reads an image file and writes a similar " + "image file to the output. It can implicitly convert from one image " + "file format to another; it uses the extension of the output filename " + "to specify the destination file format."); + + add_option + ("chan", "channels", 50, + "Elevate (or truncate) the image to the indicated number of channels. " + "This may be 1, 2, 3, or 4. You may also specify one of the keywords " + "l, la, rgb, or rgba, respectively, or any of the keywords r, g, b, or " + "a to extract out just the indicated channel as a single grayscale " + "image.", + &ImageTrans::dispatch_channels, nullptr, &_channels); + + add_option + ("cscale", "r,g,b[,a]", 50, + "Apply the indicated color scale to each pixel of the image.", + &ImageTrans::dispatch_color, &_has_color_scale, &_color_scale); + + add_option + ("flip", "", 50, + "Flip the image vertically.", + &ImageTrans::dispatch_none, &_flip); + + add_option + ("mirror", "", 50, + "Reverse the image horizontally.", + &ImageTrans::dispatch_none, &_mirror); + + add_option + ("cw", "", 50, + "Rotate the image 90 degrees clockwise.", + &ImageTrans::dispatch_none, &_cw); + + add_option + ("ccw", "", 50, + "Rotate the image 90 degrees counter-clockwise.", + &ImageTrans::dispatch_none, &_ccw); + + _channels = C_default; + _color_scale.set(1.0f, 1.0f, 1.0f, 1.0f); +} + +/** + * + */ +void ImageTrans:: +run() { + switch (_channels) { + case C_default: + break; + + case C_l: + case C_la: + case C_rgb: + case C_rgba: + _image.set_num_channels((int)_channels); + break; + + case C_r: + _image.make_grayscale(1.0, 0.0, 0.0); + _image.remove_alpha(); + break; + + case C_g: + _image.make_grayscale(0.0, 1.0, 0.0); + _image.remove_alpha(); + break; + + case C_b: + _image.make_grayscale(0.0, 0.0, 1.0); + _image.remove_alpha(); + break; + + case C_a: + extract_alpha(); + break; + } + + if (_has_color_scale) { + if (_color_scale[0] != 1.0f || + _color_scale[1] != 1.0f || + _color_scale[2] != 1.0f) { + for (int yi = 0; yi < _image.get_y_size(); ++yi) { + for (int xi = 0; xi < _image.get_x_size(); ++xi) { + LRGBColorf rgb = _image.get_xel(xi, yi); + _image.set_xel(xi, yi, + rgb[0] * _color_scale[0], + rgb[1] * _color_scale[1], + rgb[2] * _color_scale[2]); + } + } + } + if (_image.has_alpha() && _color_scale[3] != 1.0f) { + for (int yi = 0; yi < _image.get_y_size(); ++yi) { + for (int xi = 0; xi < _image.get_x_size(); ++xi) { + PN_stdfloat a = _image.get_alpha(xi, yi); + _image.set_alpha(xi, yi, a * _color_scale[3]); + } + } + } + } + + bool transpose = false; + if (_cw) { + _flip = !_flip; + transpose = !transpose; + } + if (_ccw) { + _mirror = !_mirror; + transpose = !transpose; + } + if (_flip || _mirror || transpose) { + _image.flip(_mirror, _flip, transpose); + } + + write_image(); +} + +/** + * Interprets the -chan parameter. + */ +bool ImageTrans:: +dispatch_channels(const std::string &opt, const std::string &arg, void *var) { + Channels *ip = (Channels *)var; + if (cmp_nocase(arg, "l") == 0) { + (*ip) = C_l; + } else if (cmp_nocase(arg, "la") == 0) { + (*ip) = C_la; + } else if (cmp_nocase(arg, "rgb") == 0) { + (*ip) = C_rgb; + } else if (cmp_nocase(arg, "rgba") == 0) { + (*ip) = C_rgba; + } else if (cmp_nocase(arg, "r") == 0) { + (*ip) = C_r; + } else if (cmp_nocase(arg, "g") == 0) { + (*ip) = C_g; + } else if (cmp_nocase(arg, "b") == 0) { + (*ip) = C_b; + } else if (cmp_nocase(arg, "a") == 0) { + (*ip) = C_a; + } else { + int value; + if (!string_to_int(arg, value)) { + nout << "Invalid parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + if (value < 1 || value > 4) { + nout << "Number of channels must be one of 1, 2, 3, or 4.\n"; + return false; + } + (*ip) = (Channels)value; + } + + return true; +} + +/** + * Extracts out just the alpha channel and stores it as a grayscale image. + */ +void ImageTrans:: +extract_alpha() { + if (!_image.has_alpha()) { + nout << "Source image does not have an alpha channel!\n"; + _image.make_grayscale(); + _image.fill(); + return; + } + + _image.make_grayscale(); + for (int y = 0; y < _image.get_y_size(); y++) { + for (int x = 0; x < _image.get_x_size(); x++) { + _image.set_gray_val(x, y, _image.get_alpha_val(x, y)); + } + } + _image.remove_alpha(); +} + + +int main(int argc, char *argv[]) { + ImageTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageTrans.h b/pandatool/src/imageprogs/imageTrans.h new file mode 100644 index 00000000..a41d3ef5 --- /dev/null +++ b/pandatool/src/imageprogs/imageTrans.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageTrans.h + * @author drose + * @date 2000-06-19 + */ + +#ifndef IMAGETRANS_H +#define IMAGETRANS_H + +#include "pandatoolbase.h" + +#include "imageFilter.h" + +/** + * A program to read an image file and write an equivalent image file, + * possibly performing some minor operations along the way. + */ +class ImageTrans : public ImageFilter { +public: + ImageTrans(); + + void run(); + +private: + static bool dispatch_channels(const std::string &opt, const std::string &arg, void *var); + void extract_alpha(); + + enum Channels { + C_default, + C_l = 1, + C_la = 2, + C_rgb = 3, + C_rgba = 4, + C_r, + C_g, + C_b, + C_a + }; + + Channels _channels; + LColor _color_scale; + bool _has_color_scale; + bool _flip, _mirror, _cw, _ccw; +}; + +#endif diff --git a/pandatool/src/imageprogs/imageTransformColors.I b/pandatool/src/imageprogs/imageTransformColors.I new file mode 100644 index 00000000..6a6ba23e --- /dev/null +++ b/pandatool/src/imageprogs/imageTransformColors.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageTransformColors.I + * @author drose + * @date 2009-03-25 + */ diff --git a/pandatool/src/imageprogs/imageTransformColors.cxx b/pandatool/src/imageprogs/imageTransformColors.cxx new file mode 100644 index 00000000..0efee4aa --- /dev/null +++ b/pandatool/src/imageprogs/imageTransformColors.cxx @@ -0,0 +1,481 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageTransformColors.cxx + * @author drose + * @date 2009-03-25 + */ + +#include "imageTransformColors.h" +#include "string_utils.h" +#include "pnmImage.h" +#include + +using std::max; +using std::min; +using std::string; + +/** + * + */ +ImageTransformColors:: +ImageTransformColors() { + set_program_brief("transform colors in an image file"); + set_program_description + ("This program can apply a global color transform to all of the " + "pixels in an image, or in a series of images. This can be used, " + "for instance, to increase or decrease the dynamic range; or to " + "rotate the hue; or to reduce the saturation of colors in the image.\n\n" + + "Each parameter is encoded in a 4x4 matrix, which modifies the R, G, B " + "colors of the image (the alpha values, if any, are not affected). " + "RGB values are clamped at 0 and 1 after the operation. " + "Multiple parameters are composed together in the order in which they " + "are listed."); + + add_option + ("hls", "", 0, + "Specifies that all of the matrix operations are performed in HLS " + "space, instead of the default RGB space. In this mode, the first " + "component controls hue, the second controls lightness, and the third " + "controls saturation.", + &ImageTransformColors::dispatch_none, &_hls, nullptr); + + add_option + ("range", "min,max", 0, + "Compresses the overall dynamic range from 0,1 to min,max. If min,max " + "exceed 0,1, the dynamic range is expanded. This doesn't make sense in " + "HLS mode.", + &ImageTransformColors::dispatch_range, nullptr, &_mat); + + add_option + ("scale", "r,g,b", 0, + "Scales the r,g,b components by the indicated values. In HLS mode, " + "the scale is applied to the h,l,s components.", + &ImageTransformColors::dispatch_scale, nullptr, &_mat); + + add_option + ("add", "r,g,b", 0, + "Adds the indicated values to the r,g,b components. In HLS mode, " + "the sum is applied to the h,l,s components.", + &ImageTransformColors::dispatch_add, nullptr, &_mat); + + add_option + ("mat4", "m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33", + 0, "Defines an arbitrary 4x4 RGB matrix.", + &ImageTransformColors::dispatch_mat4, nullptr, &_mat); + + add_option + ("mat3", "m00,m01,m02,m10,m11,m12,m20,m21,m22", 0, + "Defines an arbitrary 3x3 RGB matrix.", + &ImageTransformColors::dispatch_mat3, nullptr, &_mat); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting image file will be written. " + "This is only valid when there is only one input image file on the command " + "line. If you want to process multiple files simultaneously, you must " + "use either -d or -inplace.", + &ImageTransformColors::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("d", "dirname", 50, + "Specify the name of the directory in which to write the resulting image " + "files. If you are processing only one image file, this may be omitted " + "in lieu of the -o option. If you are processing multiple image files, " + "this may be omitted only if you specify -inplace instead.", + &ImageTransformColors::dispatch_filename, &_got_output_dirname, &_output_dirname); + + add_option + ("inplace", "", 50, + "If this option is given, the input image files will be rewritten in " + "place with the results. This obviates the need to specify -d " + "for an output directory; however, it's risky because the original " + "input image files are lost.", + &ImageTransformColors::dispatch_none, &_inplace); + + _mat = LMatrix4d::ident_mat(); +} + +/** + * + */ +void ImageTransformColors:: +run() { + _mat.write(nout, 0); + nout << "\n"; + + Filenames::iterator fi; + for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) { + const Filename &source_filename = (*fi); + nout << source_filename << "\n"; + PNMImage image; + if (!image.read(source_filename)) { + nout << "Couldn't read " << source_filename << "; ignoring.\n"; + continue; + } + + process_image(image); + + Filename output_filename = get_output_filename(source_filename); + if (!image.write(output_filename)) { + nout << "Couldn't write " << output_filename << "; ignoring.\n"; + } + } +} + +/** + * Takes a series of 16 numbers as a 4x4 matrix. + */ +bool ImageTransformColors:: +dispatch_mat4(const string &opt, const string &arg, void *var) { + LMatrix4d &orig = *(LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + LMatrix4d mat; + bool okflag = false; + if (words.size() == 16) { + okflag = + string_to_double(words[0], mat[0][0]) && + string_to_double(words[1], mat[0][1]) && + string_to_double(words[2], mat[0][2]) && + string_to_double(words[3], mat[0][3]) && + string_to_double(words[4], mat[1][0]) && + string_to_double(words[5], mat[1][1]) && + string_to_double(words[6], mat[1][2]) && + string_to_double(words[7], mat[1][3]) && + string_to_double(words[8], mat[2][0]) && + string_to_double(words[9], mat[2][1]) && + string_to_double(words[10], mat[2][2]) && + string_to_double(words[11], mat[2][3]) && + string_to_double(words[12], mat[3][0]) && + string_to_double(words[13], mat[3][1]) && + string_to_double(words[14], mat[3][2]) && + string_to_double(words[15], mat[3][3]); + } + + if (!okflag) { + nout << "-" << opt + << " requires sixteen numbers separated by commas.\n"; + return false; + } + + orig *= mat; + + return true; +} + +/** + * Takes a series of 9 numbers as a 3x3 matrix. + */ +bool ImageTransformColors:: +dispatch_mat3(const string &opt, const string &arg, void *var) { + LMatrix4d &orig = *(LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + LMatrix3d mat; + bool okflag = false; + if (words.size() == 9) { + okflag = + string_to_double(words[0], mat[0][0]) && + string_to_double(words[1], mat[0][1]) && + string_to_double(words[2], mat[0][2]) && + string_to_double(words[3], mat[1][0]) && + string_to_double(words[4], mat[1][1]) && + string_to_double(words[5], mat[1][2]) && + string_to_double(words[6], mat[2][0]) && + string_to_double(words[7], mat[2][1]) && + string_to_double(words[8], mat[2][2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires nine numbers separated by commas.\n"; + return false; + } + + orig *= LMatrix4d(mat); + + return true; +} + +/** + * Takes a min,max dynamic range. + */ +bool ImageTransformColors:: +dispatch_range(const string &opt, const string &arg, void *var) { + LMatrix4d &orig = *(LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + double min, max; + bool okflag = false; + if (words.size() == 2) { + okflag = + string_to_double(words[0], min) && + string_to_double(words[1], max); + } + + if (!okflag) { + nout << "-" << opt + << " requires two numbers separated by commas.\n"; + return false; + } + + orig *= LMatrix4d::scale_mat(max - min) * LMatrix4d::translate_mat(min); + + return true; +} + +/** + * Accepts a componentwise scale. + */ +bool ImageTransformColors:: +dispatch_scale(const string &opt, const string &arg, void *var) { + LMatrix4d &orig = *(LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + double r, g, b; + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], r) && + string_to_double(words[1], g) && + string_to_double(words[2], b); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + orig *= LMatrix4d::scale_mat(r, g, b); + + return true; +} + +/** + * Accepts a componentwise add. + */ +bool ImageTransformColors:: +dispatch_add(const string &opt, const string &arg, void *var) { + LMatrix4d &orig = *(LMatrix4d *)var; + + vector_string words; + tokenize(arg, words, ","); + + double r, g, b; + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], r) && + string_to_double(words[1], g) && + string_to_double(words[2], b); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + orig *= LMatrix4d::translate_mat(r, g, b); + + return true; +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool ImageTransformColors:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the image file(s) to read on the command line.\n"; + return false; + + } else { + // These only apply if we have specified any image files. + if (_got_output_filename && args.size() == 1) { + if (_got_output_dirname) { + nout << "Cannot specify both -o and -d.\n"; + return false; + } else if (_inplace) { + nout << "Cannot specify both -o and -inplace.\n"; + return false; + } + + } else { + if (_got_output_filename) { + nout << "Cannot use -o when multiple image files are specified.\n"; + return false; + } + + if (_got_output_dirname && _inplace) { + nout << "Cannot specify both -inplace and -d.\n"; + return false; + + } else if (!_got_output_dirname && !_inplace) { + nout << "You must specify either -inplace or -d.\n"; + return false; + } + } + } + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + Filename filename = (*ai); + if (!filename.exists()) { + nout << "Image file not found: " << filename << "\n"; + return false; + } + _filenames.push_back(filename); + } + + return true; +} + +/** + * Returns the output filename of the egg file with the given input filename. + * This is based on the user's choice of -inplace, -o, or -d. + */ +Filename ImageTransformColors:: +get_output_filename(const Filename &source_filename) const { + if (_got_output_filename) { + nassertr(!_inplace && !_got_output_dirname && _filenames.size() == 1, Filename()); + return _output_filename; + + } else if (_got_output_dirname) { + nassertr(!_inplace, Filename()); + Filename result = source_filename; + result.set_dirname(_output_dirname); + return result; + } + + nassertr(_inplace, Filename()); + return source_filename; +} + +inline double +hue2rgb(double m1, double m2, double h) { + h -= floor(h); + if (h < 1.0/6.0) { + return m1 + (m2 - m1) * h * 6.0; + } + if (h < 1.0/2.0) { + return m2; + } + if (h < 2.0/3.0) { + return m1 + (m2 - m1) * (2.0/3.0 - h) * 6; + } + return m1; +} + +static LRGBColord +hls2rgb(const LRGBColord &hls) { + double h = hls[0]; + double l = max(min(hls[1], 1.0), 0.0); + double s = max(min(hls[2], 1.0), 0.0); + + double m2; + if (l <= 0.5) { + m2 = l * (s + 1.0); + } else { + m2 = l + s - l * s; + } + double m1 = l * 2 - m2; + + LRGBColord rgb(hue2rgb(m1, m2, h + 1.0/3.0), + hue2rgb(m1, m2, h), + hue2rgb(m1, m2, h - 1.0/3.0)); + return rgb; +} + +static LRGBColord +rgb2hls(const LRGBColord &rgb) { + double r = rgb[0]; + double g = rgb[1]; + double b = rgb[2]; + double h, l, s; + + double minval = min(min(r, g), b); + double maxval = max(max(r, g), b); + + double rnorm = 0.0, gnorm = 0.0, bnorm = 0.0; + double mdiff = maxval - minval; + double msum = maxval + minval; + l = 0.5 * msum; + if (maxval == minval) { + // Grayscale. + return LRGBColord(0.0, l, 0.0); + } + + rnorm = (maxval - r) / mdiff; + gnorm = (maxval - g) / mdiff; + bnorm = (maxval - b) / mdiff; + + if (l < 0.5) { + s = mdiff / msum; + } else { + s = mdiff / (2.0 - msum); + } + + if (r == maxval) { + h = (6.0 + bnorm - gnorm) / 6.0; + } else if (g == maxval) { + h = (2.0 + rnorm - bnorm) / 6.0; + } else { + h = (4.0 + gnorm - rnorm) / 6.0; + } + + if (h > 1.0) { + h -= 1.0; + } + + return LRGBColord(h, l, s); +} + +/** + * Processes a single image in-place. + */ +void ImageTransformColors:: +process_image(PNMImage &image) { + if (_hls) { + for (int yi = 0; yi < image.get_y_size(); ++yi) { + for (int xi = 0; xi < image.get_x_size(); ++xi) { + LRGBColord rgb = LCAST(double, image.get_xel(xi, yi)); + rgb = hls2rgb(_mat.xform_point(rgb2hls(rgb))); + image.set_xel(xi, yi, LCAST(float, rgb)); + } + } + } else { + for (int yi = 0; yi < image.get_y_size(); ++yi) { + for (int xi = 0; xi < image.get_x_size(); ++xi) { + LRGBColord rgb = LCAST(double, image.get_xel(xi, yi)); + rgb = _mat.xform_point(rgb); + image.set_xel(xi, yi, LCAST(float, rgb)); + } + } + } +} + +int main(int argc, char *argv[]) { + ImageTransformColors prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/imageprogs/imageTransformColors.h b/pandatool/src/imageprogs/imageTransformColors.h new file mode 100644 index 00000000..a6d11bb1 --- /dev/null +++ b/pandatool/src/imageprogs/imageTransformColors.h @@ -0,0 +1,63 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageTransformColors.h + * @author drose + * @date 2009-03-25 + */ + +#ifndef IMAGETRANSFORMCOLORS_H +#define IMAGETRANSFORMCOLORS_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "pvector.h" +#include "filename.h" +#include "luse.h" + +class PNMImage; + +/** + * This program can apply a 4x4 color transform to all of the colors in the + * pixels of a series of images. + */ +class ImageTransformColors : public ProgramBase { +public: + ImageTransformColors(); + + void run(); + +protected: + static bool dispatch_mat4(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_mat3(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_range(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_scale(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_add(const std::string &opt, const std::string &arg, void *var); + + virtual bool handle_args(Args &args); + Filename get_output_filename(const Filename &source_filename) const; + + void process_image(PNMImage &image); + +private: + bool _hls; + LMatrix4d _mat; + + bool _got_output_filename; + Filename _output_filename; + bool _got_output_dirname; + Filename _output_dirname; + bool _inplace; + + typedef pvector Filenames; + Filenames _filenames; +}; + +#include "imageTransformColors.I" + +#endif diff --git a/pandatool/src/lwo/CMakeLists.txt b/pandatool/src/lwo/CMakeLists.txt new file mode 100644 index 00000000..c7c7f33c --- /dev/null +++ b/pandatool/src/lwo/CMakeLists.txt @@ -0,0 +1,69 @@ +set(P3LWO_HEADERS + config_lwo.h + iffChunk.h iffChunk.I + iffGenericChunk.h iffGenericChunk.I + iffId.h iffId.I + iffInputFile.h iffInputFile.I + lwoBoundingBox.h + lwoChunk.h + lwoClip.h + lwoDiscontinuousVertexMap.h + lwoGroupChunk.h + lwoHeader.h lwoHeader.I + lwoInputFile.h lwoInputFile.I + lwoLayer.h + lwoPoints.h + lwoPolygons.h lwoPolygonTags.h + lwoStillImage.h + lwoSurfaceBlockAxis.h lwoSurfaceBlockChannel.h lwoSurfaceBlockCoordSys.h + lwoSurfaceBlockEnabled.h lwoSurfaceBlock.h lwoSurfaceBlockHeader.h + lwoSurfaceBlockImage.h lwoSurfaceBlockOpacity.h lwoSurfaceBlockProjection.h + lwoSurfaceBlockRefObj.h lwoSurfaceBlockRepeat.h lwoSurfaceBlockTMap.h + lwoSurfaceBlockTransform.h lwoSurfaceBlockVMapName.h lwoSurfaceBlockWrap.h + lwoSurfaceColor.h + lwoSurface.h + lwoSurfaceParameter.h + lwoSurfaceSidedness.h + lwoSurfaceSmoothingAngle.h + lwoTags.h + lwoVertexMap.h +) + +set(P3LWO_SOURCES + config_lwo.cxx + iffChunk.cxx + iffGenericChunk.cxx + iffId.cxx + iffInputFile.cxx + lwoBoundingBox.cxx + lwoChunk.cxx + lwoClip.cxx + lwoDiscontinuousVertexMap.cxx + lwoGroupChunk.cxx + lwoHeader.cxx + lwoInputFile.cxx + lwoLayer.cxx + lwoPoints.cxx + lwoPolygons.cxx + lwoPolygonTags.cxx + lwoStillImage.cxx + lwoSurfaceBlockAxis.cxx lwoSurfaceBlockChannel.cxx lwoSurfaceBlockCoordSys.cxx + lwoSurfaceBlock.cxx lwoSurfaceBlockEnabled.cxx lwoSurfaceBlockHeader.cxx + lwoSurfaceBlockImage.cxx lwoSurfaceBlockOpacity.cxx lwoSurfaceBlockProjection.cxx + lwoSurfaceBlockRefObj.cxx lwoSurfaceBlockRepeat.cxx lwoSurfaceBlockTMap.cxx + lwoSurfaceBlockTransform.cxx lwoSurfaceBlockVMapName.cxx lwoSurfaceBlockWrap.cxx + lwoSurfaceColor.cxx + lwoSurface.cxx + lwoSurfaceParameter.cxx + lwoSurfaceSidedness.cxx + lwoSurfaceSmoothingAngle.cxx + lwoTags.cxx + lwoVertexMap.cxx +) + +composite_sources(p3lwo P3LWO_SOURCES) +add_library(p3lwo STATIC ${P3LWO_HEADERS} ${P3LWO_SOURCES}) +target_link_libraries(p3lwo p3pandatoolbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/lwo/config_lwo.cxx b/pandatool/src/lwo/config_lwo.cxx new file mode 100644 index 00000000..de7d9247 --- /dev/null +++ b/pandatool/src/lwo/config_lwo.cxx @@ -0,0 +1,112 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_lwo.cxx + * @author drose + * @date 2001-04-23 + */ + +#include "config_lwo.h" +#include "iffChunk.h" +#include "iffGenericChunk.h" +#include "iffInputFile.h" +#include "lwoBoundingBox.h" +#include "lwoChunk.h" +#include "lwoClip.h" +#include "lwoDiscontinuousVertexMap.h" +#include "lwoGroupChunk.h" +#include "lwoHeader.h" +#include "lwoInputFile.h" +#include "lwoLayer.h" +#include "lwoPoints.h" +#include "lwoPolygons.h" +#include "lwoPolygonTags.h" +#include "lwoStillImage.h" +#include "lwoSurface.h" +#include "lwoSurfaceBlock.h" +#include "lwoSurfaceBlockAxis.h" +#include "lwoSurfaceBlockChannel.h" +#include "lwoSurfaceBlockCoordSys.h" +#include "lwoSurfaceBlockEnabled.h" +#include "lwoSurfaceBlockImage.h" +#include "lwoSurfaceBlockOpacity.h" +#include "lwoSurfaceBlockProjection.h" +#include "lwoSurfaceBlockHeader.h" +#include "lwoSurfaceBlockRefObj.h" +#include "lwoSurfaceBlockRepeat.h" +#include "lwoSurfaceBlockTMap.h" +#include "lwoSurfaceBlockTransform.h" +#include "lwoSurfaceBlockVMapName.h" +#include "lwoSurfaceBlockWrap.h" +#include "lwoSurfaceColor.h" +#include "lwoSurfaceParameter.h" +#include "lwoSurfaceSidedness.h" +#include "lwoSurfaceSmoothingAngle.h" +#include "lwoTags.h" +#include "lwoVertexMap.h" + +#include "dconfig.h" + +Configure(config_lwo); + +ConfigureFn(config_lwo) { + init_liblwo(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_liblwo() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + IffChunk::init_type(); + IffGenericChunk::init_type(); + IffInputFile::init_type(); + LwoBoundingBox::init_type(); + LwoChunk::init_type(); + LwoClip::init_type(); + LwoDiscontinuousVertexMap::init_type(); + LwoGroupChunk::init_type(); + LwoHeader::init_type(); + LwoInputFile::init_type(); + LwoLayer::init_type(); + LwoPoints::init_type(); + LwoPolygons::init_type(); + LwoPolygonTags::init_type(); + LwoTags::init_type(); + LwoStillImage::init_type(); + LwoSurface::init_type(); + LwoSurfaceBlock::init_type(); + LwoSurfaceBlockAxis::init_type(); + LwoSurfaceBlockChannel::init_type(); + LwoSurfaceBlockCoordSys::init_type(); + LwoSurfaceBlockEnabled::init_type(); + LwoSurfaceBlockImage::init_type(); + LwoSurfaceBlockOpacity::init_type(); + LwoSurfaceBlockProjection::init_type(); + LwoSurfaceBlockHeader::init_type(); + LwoSurfaceBlockRefObj::init_type(); + LwoSurfaceBlockRepeat::init_type(); + LwoSurfaceBlockTMap::init_type(); + LwoSurfaceBlockTransform::init_type(); + LwoSurfaceBlockVMapName::init_type(); + LwoSurfaceBlockWrap::init_type(); + LwoSurfaceColor::init_type(); + LwoSurfaceParameter::init_type(); + LwoSurfaceSidedness::init_type(); + LwoSurfaceSmoothingAngle::init_type(); + LwoVertexMap::init_type(); +} diff --git a/pandatool/src/lwo/config_lwo.h b/pandatool/src/lwo/config_lwo.h new file mode 100644 index 00000000..b2ed9850 --- /dev/null +++ b/pandatool/src/lwo/config_lwo.h @@ -0,0 +1,21 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_lwo.h + * @author drose + * @date 2001-04-23 + */ + +#ifndef CONFIG_LWO_H +#define CONFIG_LWO_H + +#include "pandatoolbase.h" + +extern void init_liblwo(); + +#endif diff --git a/pandatool/src/lwo/iffChunk.I b/pandatool/src/lwo/iffChunk.I new file mode 100644 index 00000000..716e8935 --- /dev/null +++ b/pandatool/src/lwo/iffChunk.I @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffChunk.I + * @author drose + * @date 2001-04-23 + */ + +/** + * + */ +INLINE IffChunk:: +IffChunk() { +} + +/** + * Returns the ID associated with this chunk. + */ +INLINE IffId IffChunk:: +get_id() const { + return _id; +} + +/** + * Changes the ID associated with this chunk. + */ +INLINE void IffChunk:: +set_id(IffId id) { + _id = id; +} diff --git a/pandatool/src/lwo/iffChunk.cxx b/pandatool/src/lwo/iffChunk.cxx new file mode 100644 index 00000000..a86ac62d --- /dev/null +++ b/pandatool/src/lwo/iffChunk.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffChunk.cxx + * @author drose + * @date 2001-04-23 + */ + +#include "iffChunk.h" +#include "iffInputFile.h" + +#include "indent.h" + +TypeHandle IffChunk::_type_handle; + +/** + * + */ +void IffChunk:: +output(std::ostream &out) const { + out << _id << " (" << get_type() << ")"; +} + +/** + * + */ +void IffChunk:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) << _id << " { ... }\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *IffChunk:: +make_new_chunk(IffInputFile *in, IffId id) { + return in->make_new_chunk(id); +} diff --git a/pandatool/src/lwo/iffChunk.h b/pandatool/src/lwo/iffChunk.h new file mode 100644 index 00000000..58de2647 --- /dev/null +++ b/pandatool/src/lwo/iffChunk.h @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffChunk.h + * @author drose + * @date 2001-04-23 + */ + +#ifndef IFFCHUNK_H +#define IFFCHUNK_H + +#include "pandatoolbase.h" + +#include "iffId.h" + +#include "typedObject.h" +#include "typedReferenceCount.h" + +class IffInputFile; + +/** + * The basic kind of record in an EA "IFF" file, which the LightWave object + * file is based on. + */ +class IffChunk : public TypedReferenceCount { +public: + INLINE IffChunk(); + + INLINE IffId get_id() const; + INLINE void set_id(IffId id); + + virtual bool read_iff(IffInputFile *in, size_t stop_at)=0; + + virtual void output(std::ostream &out) const; + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + +private: + IffId _id; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "IffChunk", + TypedReferenceCount::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "iffChunk.I" + +INLINE std::ostream &operator << (std::ostream &out, const IffChunk &chunk) { + chunk.output(out); + return out; +} + +#endif diff --git a/pandatool/src/lwo/iffGenericChunk.I b/pandatool/src/lwo/iffGenericChunk.I new file mode 100644 index 00000000..bfa0a138 --- /dev/null +++ b/pandatool/src/lwo/iffGenericChunk.I @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffGenericChunk.I + * @author drose + * @date 2001-04-23 + */ + +/** + * + */ +INLINE IffGenericChunk:: +IffGenericChunk() { +} + +/** + * Returns the data in the chunk. + */ +INLINE const Datagram &IffGenericChunk:: +get_data() const { + return _data; +} + +/** + * Changes the data in the chunk + */ +INLINE void IffGenericChunk:: +set_data(const Datagram &data) { + _data = data; +} diff --git a/pandatool/src/lwo/iffGenericChunk.cxx b/pandatool/src/lwo/iffGenericChunk.cxx new file mode 100644 index 00000000..c8eaf015 --- /dev/null +++ b/pandatool/src/lwo/iffGenericChunk.cxx @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffGenericChunk.cxx + * @author drose + * @date 2001-04-23 + */ + +#include "iffGenericChunk.h" +#include "iffInputFile.h" + +#include "indent.h" + +TypeHandle IffGenericChunk::_type_handle; + + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool IffGenericChunk:: +read_iff(IffInputFile *in, size_t stop_at) { + size_t length = stop_at - in->get_bytes_read(); + bool result = in->read_bytes(_data, length); + in->align(); + return result; +} + +/** + * + */ +void IffGenericChunk:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { " << _data.get_length() << " bytes }\n"; +} diff --git a/pandatool/src/lwo/iffGenericChunk.h b/pandatool/src/lwo/iffGenericChunk.h new file mode 100644 index 00000000..e1e99024 --- /dev/null +++ b/pandatool/src/lwo/iffGenericChunk.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffGenericChunk.h + * @author drose + * @date 2001-04-23 + */ + +#ifndef IFFGENERICCHUNK_H +#define IFFGENERICCHUNK_H + +#include "pandatoolbase.h" + +#include "iffChunk.h" + +#include "datagram.h" + + +/** + * A class for a generic kind of IffChunk that is not understood by a + * particular IffReader. It remembers its entire contents. + */ +class IffGenericChunk : public IffChunk { +public: + INLINE IffGenericChunk(); + + INLINE const Datagram &get_data() const; + INLINE void set_data(const Datagram &data); + + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + Datagram _data; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + IffChunk::init_type(); + register_type(_type_handle, "IffGenericChunk", + IffChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "iffGenericChunk.I" + +#endif diff --git a/pandatool/src/lwo/iffId.I b/pandatool/src/lwo/iffId.I new file mode 100644 index 00000000..938e26c4 --- /dev/null +++ b/pandatool/src/lwo/iffId.I @@ -0,0 +1,84 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffId.I + * @author drose + * @date 2001-04-23 + */ + +/** + * + */ +INLINE IffId:: +IffId() { + _id._c[0] = 0; + _id._c[1] = 0; + _id._c[2] = 0; + _id._c[3] = 0; +} + +/** + * + */ +INLINE IffId:: +IffId(const char id[4]) { + _id._c[0] = id[0]; + _id._c[1] = id[1]; + _id._c[2] = id[2]; + _id._c[3] = id[3]; +} + +/** + * + */ +INLINE IffId:: +IffId(const IffId ©) { + _id._n = copy._id._n; +} + +/** + * + */ +INLINE void IffId:: +operator = (const IffId ©) { + _id._n = copy._id._n; +} + +/** + * + */ +INLINE bool IffId:: +operator == (const IffId &other) const { + return (_id._n == other._id._n); +} + +/** + * + */ +INLINE bool IffId:: +operator != (const IffId &other) const { + return (_id._n != other._id._n); +} + +/** + * The ordering is arbitrary, and may not even be consistent between different + * architectures (e.g. big-endian and little-endian). It is useful mainly + * for putting IffId's into a sorted container, like sets and maps. + */ +INLINE bool IffId:: +operator < (const IffId &other) const { + return (_id._n < other._id._n); +} + +/** + * Returns the four-character name of the Id, for outputting. + */ +INLINE std::string IffId:: +get_name() const { + return std::string(_id._c, 4); +} diff --git a/pandatool/src/lwo/iffId.cxx b/pandatool/src/lwo/iffId.cxx new file mode 100644 index 00000000..eda63c0b --- /dev/null +++ b/pandatool/src/lwo/iffId.cxx @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffId.cxx + * @author drose + * @date 2001-04-23 + */ + +#include "iffId.h" + +#include + +/** + * + */ +void IffId:: +output(std::ostream &out) const { + // If all of the characters are printable, just output them. + if (isprint(_id._c[0]) && isprint(_id._c[1]) && + isprint(_id._c[2]) && isprint(_id._c[3])) { + out << _id._c[0] << _id._c[1] << _id._c[2] << _id._c[3]; + + } else if (isprint(_id._c[0]) && isprint(_id._c[1]) && + isprint(_id._c[2]) && _id._c[3] == '\0') { + // If the last character is 0, output a 3-letter ID. + out << _id._c[0] << _id._c[1] << _id._c[2]; + + } else { + // Otherwise, write out the hex. + out << "0x" << std::hex << std::setfill('0'); + for (int i = 0; i < 4; i++) { + out << std::setw(2) << (int)(unsigned char)_id._c[i]; + } + out << std::dec << std::setfill(' '); + } +} diff --git a/pandatool/src/lwo/iffId.h b/pandatool/src/lwo/iffId.h new file mode 100644 index 00000000..c3d14a7a --- /dev/null +++ b/pandatool/src/lwo/iffId.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffId.h + * @author drose + * @date 2001-04-23 + */ + +#ifndef IFFID_H +#define IFFID_H + +#include "pandatoolbase.h" + +#include "numeric_types.h" + +/** + * A four-byte chunk ID appearing in an "IFF" file. This is used to identify + * the meaning of each chunk, and can be treated either as a concrete object + * or as a string, something like a TypeHandle. + */ +class IffId { +public: + INLINE IffId(); + INLINE IffId(const char id[4]); + INLINE IffId(const IffId ©); + INLINE void operator = (const IffId ©); + + INLINE bool operator == (const IffId &other) const; + INLINE bool operator != (const IffId &other) const; + INLINE bool operator < (const IffId &other) const; + + INLINE std::string get_name() const; + + void output(std::ostream &out) const; + +private: + union { + uint32_t _n; + char _c[4]; + } _id; +}; + +#include "iffId.I" + +INLINE std::ostream &operator << (std::ostream &out, const IffId &id) { + id.output(out); + return out; +} + +#endif diff --git a/pandatool/src/lwo/iffInputFile.I b/pandatool/src/lwo/iffInputFile.I new file mode 100644 index 00000000..18fe7e90 --- /dev/null +++ b/pandatool/src/lwo/iffInputFile.I @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffInputFile.I + * @author drose + * @date 2001-04-24 + */ + +/** + * Indicates the filename that the InputFile is currently opened on. + */ +INLINE void IffInputFile:: +set_filename(const Filename &filename) { + _filename = filename; +} + +/** + * Returns the filename that the InputFile is currently opened on, if + * available. + */ +INLINE const Filename &IffInputFile:: +get_filename() const { + return _filename; +} + +/** + * Returns true if the last read operation failed because of reaching EOF, + * false otherwise. + */ +INLINE bool IffInputFile:: +is_eof() const { + return _eof; +} + +/** + * Returns the number of bytes read so far from the input file. + */ +INLINE size_t IffInputFile:: +get_bytes_read() const { + return _bytes_read; +} + +/** + * If the current file pointer is not positioned on an even-byte boundary, + * reads and discards one byte so that it is. + */ +INLINE void IffInputFile:: +align() { + if ((_bytes_read & 1) != 0) { + get_int8(); + } +} diff --git a/pandatool/src/lwo/iffInputFile.cxx b/pandatool/src/lwo/iffInputFile.cxx new file mode 100644 index 00000000..f00dbf54 --- /dev/null +++ b/pandatool/src/lwo/iffInputFile.cxx @@ -0,0 +1,369 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffInputFile.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "iffInputFile.h" +#include "iffGenericChunk.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "virtualFileSystem.h" + +TypeHandle IffInputFile::_type_handle; + +/** + * + */ +IffInputFile:: +IffInputFile() { + _input = nullptr; + _owns_istream = false; + _eof = true; + _unexpected_eof = false; + _bytes_read = 0; +} + +/** + * + */ +IffInputFile:: +~IffInputFile() { + if (_owns_istream) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->close_read_file(_input); + } +} + +/** + * Attempts to open the indicated filename for reading. Returns true if + * successful, false otherwise. + */ +bool IffInputFile:: +open_read(Filename filename) { + filename.set_binary(); + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *in = vfs->open_read_file(filename, true); + if (in == nullptr) { + return false; + } + + set_input(in, true); + set_filename(filename); + + return true; +} + +/** + * Sets up the input to use an arbitrary istream. If owns_istream is true, + * the istream will be deleted (via vfs->close_read_file()) when the + * IffInputFile destructs. + */ +void IffInputFile:: +set_input(std::istream *input, bool owns_istream) { + if (_owns_istream) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->close_read_file(_input); + } + _input = input; + _owns_istream = owns_istream; + _eof = false; + _unexpected_eof = false; + _bytes_read = 0; +} + +/** + * Extracts a signed 8-bit integer. + */ +int8_t IffInputFile:: +get_int8() { + Datagram dg; + if (!read_bytes(dg, 1)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_int8(); +} + +/** + * Extracts an unsigned 8-bit integer. + */ +uint8_t IffInputFile:: +get_uint8() { + Datagram dg; + if (!read_bytes(dg, 1)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_int8(); +} + +/** + * Extracts a signed 16-bit big-endian integer. + */ +int16_t IffInputFile:: +get_be_int16() { + Datagram dg; + if (!read_bytes(dg, 2)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_be_int16(); +} + +/** + * Extracts a signed 32-bit big-endian integer. + */ +int32_t IffInputFile:: +get_be_int32() { + Datagram dg; + if (!read_bytes(dg, 4)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_be_int32(); +} + +/** + * Extracts an unsigned 16-bit big-endian integer. + */ +uint16_t IffInputFile:: +get_be_uint16() { + Datagram dg; + if (!read_bytes(dg, 2)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_be_uint16(); +} + +/** + * Extracts an unsigned 32-bit big-endian integer. + */ +uint32_t IffInputFile:: +get_be_uint32() { + Datagram dg; + if (!read_bytes(dg, 4)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_be_uint32(); +} + +/** + * Extracts a 32-bit big-endian single-precision floating-point number. + */ +PN_stdfloat IffInputFile:: +get_be_float32() { + Datagram dg; + if (!read_bytes(dg, 4)) { + return 0; + } + DatagramIterator dgi(dg); + return dgi.get_be_float32(); +} + +/** + * Extracts a null-terminated string. + */ +std::string IffInputFile:: +get_string() { + std::string result; + char byte; + while (read_byte(byte)) { + if (byte == 0) { + break; + } + result += byte; + } + + align(); + return result; +} + +/** + * Extracts a 4-character IFF ID. + */ +IffId IffInputFile:: +get_id() { + Datagram dg; + if (!read_bytes(dg, 4)) { + return IffId(); + } + const char *id = (const char *)dg.get_data(); + return IffId(id); +} + +/** + * Reads a single IffChunk, determining its type based on its ID. Allocates + * and returns a new IffChunk object of the appropriate type. Returns NULL if + * EOF is reached before the chunk can be read completely, or if there is some + * other error in reading the chunk. + */ +PT(IffChunk) IffInputFile:: +get_chunk() { + if (is_eof()) { + return nullptr; + } + + IffId id = get_id(); + uint32_t length = get_be_uint32(); + + if (!is_eof()) { + PT(IffChunk) chunk = make_new_chunk(id); + chunk->set_id(id); + + size_t start_point = get_bytes_read(); + size_t end_point = start_point + length; + + if (chunk->read_iff(this, end_point)) { + if (is_eof()) { + if (!_unexpected_eof) { + nout << "Unexpected EOF on file reading " << *chunk << "\n"; + _unexpected_eof = true; + } + return nullptr; + } + + size_t num_bytes_read = get_bytes_read() - start_point; + if (num_bytes_read > length) { + nout << *chunk << " read " << num_bytes_read + << " instead of " << length << " bytes.\n"; + return nullptr; + + } else if (num_bytes_read < length) { + size_t skip_count = length - num_bytes_read; + nout << "Ignoring " << skip_count << " bytes at the end of " + << *chunk << "\n"; + skip_bytes(skip_count); + } + return chunk; + } + } + + return nullptr; +} + +/** + * Similar to get_chunk(), except the chunk size is only a 16-bit number + * instead of 32-bit, and it takes a context, which is the chunk in which this + * chunk is encountered. The parent chunk may (or may not) decide what kind + * of chunk is meant by the various id's encountered. + */ +PT(IffChunk) IffInputFile:: +get_subchunk(IffChunk *context) { + if (is_eof()) { + return nullptr; + } + + IffId id = get_id(); + uint16_t length = get_be_uint16(); + + if (!is_eof()) { + PT(IffChunk) chunk = context->make_new_chunk(this, id); + chunk->set_id(id); + + size_t start_point = get_bytes_read(); + size_t end_point = start_point + length; + + if (chunk->read_iff(this, end_point)) { + if (is_eof()) { + if (!_unexpected_eof) { + nout << "Unexpected EOF on file reading " << *chunk << "\n"; + _unexpected_eof = true; + } + return nullptr; + } + + size_t num_bytes_read = get_bytes_read() - start_point; + if (num_bytes_read > length) { + nout << *chunk << " read " << num_bytes_read + << " instead of " << length << " bytes.\n"; + return nullptr; + + } else if (num_bytes_read < length) { + size_t skip_count = length - num_bytes_read; + nout << "Ignoring " << skip_count << " bytes at the end of " + << *chunk << "\n"; + skip_bytes(skip_count); + } + return chunk; + } + } + + return nullptr; +} + +/** + * Reads a single byte. Returns true if successful, false otherwise. + */ +bool IffInputFile:: +read_byte(char &byte) { + if (is_eof()) { + return false; + } + + _input->get(byte); + _bytes_read++; + _eof = _input->eof() || _input->fail(); + return !is_eof(); +} + +/** + * Reads a series of bytes, and stores them in the indicated Datagram. + * Returns true if successful, false otherwise. + */ +bool IffInputFile:: +read_bytes(Datagram &datagram, int length) { + if (is_eof()) { + return false; + } + + char *buffer = new char[length]; + _input->read(buffer, length); + _eof = (_input->gcount() != length); + if (is_eof()) { + return false; + } + + _bytes_read += length; + datagram = Datagram(buffer, length); + delete[] buffer; + return true; +} + +/** + * Reads a series of bytes, but does not store them. Returns true if + * successful, false otherwise. + */ +bool IffInputFile:: +skip_bytes(int length) { + if (is_eof()) { + return false; + } + + char byte; + while (length > 0 && !is_eof()) { + read_byte(byte); + length--; + } + + return !is_eof(); +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID. + */ +IffChunk *IffInputFile:: +make_new_chunk(IffId) { + return new IffGenericChunk; +} diff --git a/pandatool/src/lwo/iffInputFile.h b/pandatool/src/lwo/iffInputFile.h new file mode 100644 index 00000000..dc39be7d --- /dev/null +++ b/pandatool/src/lwo/iffInputFile.h @@ -0,0 +1,98 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file iffInputFile.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef IFFINPUTFILE_H +#define IFFINPUTFILE_H + +#include "pandatoolbase.h" + +#include "iffId.h" +#include "iffChunk.h" + +#include "typedObject.h" +#include "pointerTo.h" + +class Datagram; + +/** + * A wrapper around an istream used for reading an IFF file. + */ +class IffInputFile : public TypedObject { +public: + IffInputFile(); + virtual ~IffInputFile(); + + bool open_read(Filename filename); + void set_input(std::istream *input, bool owns_istream); + + INLINE void set_filename(const Filename &filename); + INLINE const Filename &get_filename() const; + + INLINE bool is_eof() const; + INLINE size_t get_bytes_read() const; + + INLINE void align(); + + int8_t get_int8(); + uint8_t get_uint8(); + + int16_t get_be_int16(); + int32_t get_be_int32(); + uint16_t get_be_uint16(); + uint32_t get_be_uint32(); + PN_stdfloat get_be_float32(); + + std::string get_string(); + + IffId get_id(); + + PT(IffChunk) get_chunk(); + PT(IffChunk) get_subchunk(IffChunk *context); + + bool read_byte(char &byte); + bool read_bytes(Datagram &datagram, int length); + bool skip_bytes(int length); + +protected: + virtual IffChunk *make_new_chunk(IffId id); + + std::istream *_input; + Filename _filename; + bool _owns_istream; + bool _eof; + bool _unexpected_eof; + size_t _bytes_read; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedObject::init_type(); + register_type(_type_handle, "IffInputFile", + TypedObject::get_class_type()); + } + +private: + static TypeHandle _type_handle; + + friend class IffChunk; +}; + +#include "iffInputFile.I" + +#endif diff --git a/pandatool/src/lwo/lwoBoundingBox.cxx b/pandatool/src/lwo/lwoBoundingBox.cxx new file mode 100644 index 00000000..10ef60a7 --- /dev/null +++ b/pandatool/src/lwo/lwoBoundingBox.cxx @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoBoundingBox.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoBoundingBox.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoBoundingBox::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoBoundingBox:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _min = lin->get_vec3(); + _max = lin->get_vec3(); + + return true; +} + +/** + * + */ +void LwoBoundingBox:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { min = " << _min << ", max = " << _max << " }\n"; +} diff --git a/pandatool/src/lwo/lwoBoundingBox.h b/pandatool/src/lwo/lwoBoundingBox.h new file mode 100644 index 00000000..993425ff --- /dev/null +++ b/pandatool/src/lwo/lwoBoundingBox.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoBoundingBox.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOBOUNDINGBOX_H +#define LWOBOUNDINGBOX_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" + +/** + * Stores the bounding box for the vertex data in a layer. Optional. + */ +class LwoBoundingBox : public LwoChunk { +public: + LVecBase3 _min; + LVecBase3 _max; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoBoundingBox", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoChunk.cxx b/pandatool/src/lwo/lwoChunk.cxx new file mode 100644 index 00000000..3abc0823 --- /dev/null +++ b/pandatool/src/lwo/lwoChunk.cxx @@ -0,0 +1,16 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoChunk.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoChunk.h" + +TypeHandle LwoChunk::_type_handle; diff --git a/pandatool/src/lwo/lwoChunk.h b/pandatool/src/lwo/lwoChunk.h new file mode 100644 index 00000000..73101597 --- /dev/null +++ b/pandatool/src/lwo/lwoChunk.h @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoChunk.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOCHUNK_H +#define LWOCHUNK_H + +#include "pandatoolbase.h" + +#include "iffChunk.h" + +/** + * A specialization of IffChunk for Lightwave Object files. Each kind of + * chunk that is specific to a Lightwave file should inherit directly or + * indirectly from LwoChunk. + */ +class LwoChunk : public IffChunk { +public: + // No particular interface here. + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + IffChunk::init_type(); + register_type(_type_handle, "LwoChunk", + IffChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoClip.cxx b/pandatool/src/lwo/lwoClip.cxx new file mode 100644 index 00000000..fd1e602a --- /dev/null +++ b/pandatool/src/lwo/lwoClip.cxx @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoClip.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoClip.h" +#include "iffInputFile.h" +#include "lwoStillImage.h" + +#include "indent.h" + +TypeHandle LwoClip::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoClip:: +read_iff(IffInputFile *in, size_t stop_at) { + _index = in->get_be_int32(); + read_subchunks_iff(in, stop_at); + return true; +} + +/** + * + */ +void LwoClip:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + indent(out, indent_level + 2) + << "index = " << _index << "\n"; + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *LwoClip:: +make_new_chunk(IffInputFile *in, IffId id) { + if (id == IffId("STIL")) { + return new LwoStillImage; + + } else { + return IffChunk::make_new_chunk(in, id); + } +} diff --git a/pandatool/src/lwo/lwoClip.h b/pandatool/src/lwo/lwoClip.h new file mode 100644 index 00000000..3955efa5 --- /dev/null +++ b/pandatool/src/lwo/lwoClip.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoClip.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOCLIP_H +#define LWOCLIP_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" + +/** + * A single image file, or a numbered sequence of images (e.g. a texture-flip + * animation). + */ +class LwoClip : public LwoGroupChunk { +public: + int _index; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoGroupChunk::init_type(); + register_type(_type_handle, "LwoClip", + LwoGroupChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoDiscontinuousVertexMap.cxx b/pandatool/src/lwo/lwoDiscontinuousVertexMap.cxx new file mode 100644 index 00000000..3e4b0c00 --- /dev/null +++ b/pandatool/src/lwo/lwoDiscontinuousVertexMap.cxx @@ -0,0 +1,124 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoDiscontinuousVertexMap.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoDiscontinuousVertexMap.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +#include + +TypeHandle LwoDiscontinuousVertexMap::_type_handle; + + +/** + * Returns true if the map has a value associated with the given index, false + * otherwise. + */ +bool LwoDiscontinuousVertexMap:: +has_value(int polygon_index, int vertex_index) const { + VMad::const_iterator di; + di = _vmad.find(polygon_index); + if (di != _vmad.end()) { + const VMap &vmap = (*di).second; + return (vmap.count(vertex_index) != 0); + } + + return false; +} + +/** + * Returns the mapping value associated with the given index, or an empty + * PTA_stdfloat if there is no mapping value associated. + */ +PTA_stdfloat LwoDiscontinuousVertexMap:: +get_value(int polygon_index, int vertex_index) const { + VMad::const_iterator di; + di = _vmad.find(polygon_index); + if (di != _vmad.end()) { + const VMap &vmap = (*di).second; + VMap::const_iterator vi; + vi = vmap.find(vertex_index); + if (vi != vmap.end()) { + return (*vi).second; + } + } + + return PTA_stdfloat(); +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoDiscontinuousVertexMap:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _map_type = lin->get_id(); + _dimension = lin->get_be_uint16(); + _name = lin->get_string(); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + int vertex_index = lin->get_vx(); + int polygon_index = lin->get_vx(); + + PTA_stdfloat value; + for (int i = 0; i < _dimension; i++) { + value.push_back(lin->get_be_float32()); + } + + VMap &vmap = _vmad[polygon_index]; + std::pair ir = + vmap.insert(VMap::value_type(vertex_index, value)); + if (!ir.second) { + // This polygonvertex pair was repeated in the vmad. Is it simply + // redundant, or is it contradictory? + PTA_stdfloat orig_value = (*ir.first).second; + + if (value.v() != orig_value.v()) { + nout << "Multiple UV values for vertex " << vertex_index + << " of polygon " << polygon_index + << " specified by discontinuous vertex map.\n" + << "Original value = "; + + PTA_stdfloat::const_iterator vi; + for (vi = orig_value.begin(); vi != orig_value.end(); ++vi) { + nout << (*vi) << " "; + } + nout << " new value = "; + for (vi = value.begin(); vi != value.end(); ++vi) { + nout << (*vi) << " "; + } + nout << "\n"; + } + } + } + + return (lin->get_bytes_read() == stop_at); +} + +/** + * + */ +void LwoDiscontinuousVertexMap:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { map_type = " << _map_type + << ", dimension = " << _dimension + << ", name = \"" << _name << "\", " + << _vmad.size() << " polygons }\n"; +} diff --git a/pandatool/src/lwo/lwoDiscontinuousVertexMap.h b/pandatool/src/lwo/lwoDiscontinuousVertexMap.h new file mode 100644 index 00000000..2f48e146 --- /dev/null +++ b/pandatool/src/lwo/lwoDiscontinuousVertexMap.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoDiscontinuousVertexMap.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWODISCONTINUOUSVERTEXMAP_H +#define LWODISCONTINUOUSVERTEXMAP_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "pta_stdfloat.h" +#include "pmap.h" + +/** + * A mapping of floating-point values per integer index. The meaning of these + * values is determined by the mapping type code and/or its name. + */ +class LwoDiscontinuousVertexMap : public LwoChunk { +public: + bool has_value(int polygon_index, int vertex_index) const; + PTA_stdfloat get_value(int polygon_index, int vertex_index) const; + + IffId _map_type; + int _dimension; + std::string _name; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef pmap VMap; + typedef pmap VMad; + VMad _vmad; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoDiscontinuousVertexMap", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoGroupChunk.cxx b/pandatool/src/lwo/lwoGroupChunk.cxx new file mode 100644 index 00000000..481f27bb --- /dev/null +++ b/pandatool/src/lwo/lwoGroupChunk.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoGroupChunk.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoGroupChunk.h" +#include "lwoInputFile.h" + +#include "pnotify.h" + +TypeHandle LwoGroupChunk::_type_handle; + +/** + * Returns the number of child chunks of this group. + */ +int LwoGroupChunk:: +get_num_chunks() const { + return _chunks.size(); +} + +/** + * Returns the nth child chunk of this group. + */ +IffChunk *LwoGroupChunk:: +get_chunk(int n) const { + nassertr(n >= 0 && n < (int)_chunks.size(), nullptr); + return _chunks[n]; +} + +/** + * Reads a sequence of child chunks, until byte stop_at has been been reached, + * and stores them as the children. Returns true if successful (and exactly + * the correct number of bytes were read), or false otherwise. + */ +bool LwoGroupChunk:: +read_chunks_iff(IffInputFile *in, size_t stop_at) { + while (in->get_bytes_read() < stop_at && !in->is_eof()) { + PT(IffChunk) chunk = in->get_chunk(); + if (chunk == nullptr) { + return false; + } + _chunks.push_back(chunk); + } + + return (in->get_bytes_read() == stop_at); +} + +/** + * Similar to read_chunks_iff(), but reads them as subchunks. + */ +bool LwoGroupChunk:: +read_subchunks_iff(IffInputFile *in, size_t stop_at) { + while (in->get_bytes_read() < stop_at && !in->is_eof()) { + PT(IffChunk) chunk = in->get_subchunk(this); + if (chunk == nullptr) { + return false; + } + _chunks.push_back(chunk); + } + + return (in->get_bytes_read() == stop_at); +} + +/** + * Formats the list of chunks for output to the user (primarily for + * debugging), one per line. + */ +void LwoGroupChunk:: +write_chunks(std::ostream &out, int indent_level) const { + Chunks::const_iterator ci; + for (ci = _chunks.begin(); ci != _chunks.end(); ++ci) { + (*ci)->write(out, indent_level); + } +} diff --git a/pandatool/src/lwo/lwoGroupChunk.h b/pandatool/src/lwo/lwoGroupChunk.h new file mode 100644 index 00000000..2a72dd79 --- /dev/null +++ b/pandatool/src/lwo/lwoGroupChunk.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoGroupChunk.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOGROUPCHUNK_H +#define LWOGROUPCHUNK_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" +#include "iffChunk.h" + +#include "pointerTo.h" + +#include "pvector.h" + +/** + * A particular kind of LwoChunk that is expected to contain an arbitrary + * number of child chunks. + */ +class LwoGroupChunk : public LwoChunk { +public: + int get_num_chunks() const; + IffChunk *get_chunk(int n) const; + +protected: + bool read_chunks_iff(IffInputFile *in, size_t stop_at); + bool read_subchunks_iff(IffInputFile *in, size_t stop_at); + void write_chunks(std::ostream &out, int indent_level) const; + + typedef pvector< PT(IffChunk) > Chunks; + Chunks _chunks; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoGroupChunk", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoHeader.I b/pandatool/src/lwo/lwoHeader.I new file mode 100644 index 00000000..2d82f6c9 --- /dev/null +++ b/pandatool/src/lwo/lwoHeader.I @@ -0,0 +1,29 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoHeader.I + * @author drose + * @date 2001-04-24 + */ + +/** + * Returns true if the header represents a valid and recognized Lightwave + * header, false otherwise. + */ +INLINE bool LwoHeader:: +is_valid() const { + return _valid; +} + +/** + * Returns the version of the Lightwave file. + */ +INLINE double LwoHeader:: +get_version() const { + return _version; +} diff --git a/pandatool/src/lwo/lwoHeader.cxx b/pandatool/src/lwo/lwoHeader.cxx new file mode 100644 index 00000000..aba632eb --- /dev/null +++ b/pandatool/src/lwo/lwoHeader.cxx @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoHeader.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoHeader.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoHeader::_type_handle; + +/** + * + */ +LwoHeader:: +LwoHeader() { + _valid = false; + _version = 0.0; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoHeader:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _lwid = lin->get_id(); + + if (_lwid == IffId("LWO2")) { + _valid = true; + _version = 6.0; + } else if (_lwid == IffId("LWOB")) { + _valid = true; + _version = 5.0; + } + + if (_valid) { + lin->set_lwo_version(_version); + } + + read_chunks_iff(lin, stop_at); + + return true; +} + +/** + * + */ +void LwoHeader:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + indent(out, indent_level + 2) + << "id = " << _lwid << "\n"; + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} diff --git a/pandatool/src/lwo/lwoHeader.h b/pandatool/src/lwo/lwoHeader.h new file mode 100644 index 00000000..539d4976 --- /dev/null +++ b/pandatool/src/lwo/lwoHeader.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoHeader.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOHEADER_H +#define LWOHEADER_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" + +/** + * The first chunk in a Lightwave Object file. + */ +class LwoHeader : public LwoGroupChunk { +public: + LwoHeader(); + + IffId _lwid; + + INLINE bool is_valid() const; + INLINE double get_version() const; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + bool _valid; + double _version; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoGroupChunk::init_type(); + register_type(_type_handle, "LwoHeader", + LwoGroupChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "lwoHeader.I" + +#endif diff --git a/pandatool/src/lwo/lwoInputFile.I b/pandatool/src/lwo/lwoInputFile.I new file mode 100644 index 00000000..7824cdbb --- /dev/null +++ b/pandatool/src/lwo/lwoInputFile.I @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoInputFile.I + * @author drose + * @date 2001-04-24 + */ + +/** + * Returns the version of the Lightwave file being read. This is unknown + * until the header record has been read; then it will be set by the header. + */ +INLINE double LwoInputFile:: +get_lwo_version() const { + return _lwo_version; +} + + +/** + * Changes the version number reported for the Lightwave file. Normally this + * is only called by LwoHeader as it is read. + */ +INLINE void LwoInputFile:: +set_lwo_version(double lwo_version) { + _lwo_version = lwo_version; +} diff --git a/pandatool/src/lwo/lwoInputFile.cxx b/pandatool/src/lwo/lwoInputFile.cxx new file mode 100644 index 00000000..a6737855 --- /dev/null +++ b/pandatool/src/lwo/lwoInputFile.cxx @@ -0,0 +1,138 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoInputFile.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoInputFile.h" +#include "lwoBoundingBox.h" +#include "lwoClip.h" +#include "lwoDiscontinuousVertexMap.h" +#include "lwoHeader.h" +#include "lwoLayer.h" +#include "lwoPoints.h" +#include "lwoPolygons.h" +#include "lwoPolygonTags.h" +#include "lwoTags.h" +#include "lwoSurface.h" +#include "lwoVertexMap.h" + +using std::string; + +TypeHandle LwoInputFile::_type_handle; + +/** + * + */ +LwoInputFile:: +LwoInputFile() { +} + +/** + * + */ +LwoInputFile:: +~LwoInputFile() { +} + +/** + * Reads a Lightwave variable-length index. This is either a 2-byte or 4-byte + * integer. + */ +int LwoInputFile:: +get_vx() { + uint16_t top = get_be_uint16(); + if ((top & 0xff00) == 0xff00) { + // The first byte is 0xff, which indicates we have a 4-byte integer. + uint16_t bottom = get_be_uint16(); + return ((int)(top & 0xff) << 16) | bottom; + } + + // The first byte is not 0xff, which indicates we have a 2-byte integer. + return top; +} + +/** + * Reads a three-component vector of floats. + */ +LVecBase3 LwoInputFile:: +get_vec3() { + LVecBase3 result; + result[0] = get_be_float32(); + result[1] = get_be_float32(); + result[2] = get_be_float32(); + return result; +} + +/** + * Reads a Lightwave platform-neutral filename and converts it to a Panda + * platform-neutral filename. + */ +Filename LwoInputFile:: +get_filename() { + string name = get_string(); + size_t colon = name.find(':'); + if (colon == string::npos) { + // No colon; it's just a relative path. + return Filename(name); + } + + // The colon separates the device and the path. + string device = name.substr(0, colon); + string path = name.substr(colon + 1); + + nout << "Ignoring filename device " << device << "\n"; + return Filename("/", path); +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID. + */ +IffChunk *LwoInputFile:: +make_new_chunk(IffId id) { + if (id == IffId("FORM")) { + return new LwoHeader; + + } else if (id == IffId("LAYR")) { + return new LwoLayer; + + } else if (id == IffId("PNTS")) { + return new LwoPoints; + + } else if (id == IffId("VMAP")) { + return new LwoVertexMap; + + } else if (id == IffId("VMAD")) { + return new LwoDiscontinuousVertexMap; + + } else if (id == IffId("POLS")) { + return new LwoPolygons; + + } else if (id == IffId("TAGS") || + id == IffId("SRFS")) { + return new LwoTags; + + } else if (id == IffId("PTAG")) { + return new LwoPolygonTags; + + } else if (id == IffId("CLIP")) { + return new LwoClip; + + } else if (id == IffId("SURF")) { + return new LwoSurface; + + } else if (id == IffId("BBOX")) { + return new LwoBoundingBox; + + } else { + return IffInputFile::make_new_chunk(id); + } +} diff --git a/pandatool/src/lwo/lwoInputFile.h b/pandatool/src/lwo/lwoInputFile.h new file mode 100644 index 00000000..59794684 --- /dev/null +++ b/pandatool/src/lwo/lwoInputFile.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoInputFile.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOINPUTFILE_H +#define LWOINPUTFILE_H + +#include "pandatoolbase.h" + +#include "iffInputFile.h" + +#include "luse.h" + +/** + * A specialization of IffInputFile to handle reading a Lightwave Object file. + */ +class LwoInputFile : public IffInputFile { +public: + LwoInputFile(); + ~LwoInputFile(); + + INLINE double get_lwo_version() const; + INLINE void set_lwo_version(double version); + + int get_vx(); + LVecBase3 get_vec3(); + Filename get_filename(); + +protected: + virtual IffChunk *make_new_chunk(IffId id); + +private: + double _lwo_version; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + IffInputFile::init_type(); + register_type(_type_handle, "LwoInputFile", + IffInputFile::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "lwoInputFile.I" + +#endif diff --git a/pandatool/src/lwo/lwoLayer.cxx b/pandatool/src/lwo/lwoLayer.cxx new file mode 100644 index 00000000..1fea2cd5 --- /dev/null +++ b/pandatool/src/lwo/lwoLayer.cxx @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoLayer.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoLayer.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoLayer::_type_handle; + +/** + * Resets the layer's parameters to initial defaults for a generic layer + * created implicitly. + */ +void LwoLayer:: +make_generic() { + _number = -1; + _flags = 0; + _pivot.set(0.0, 0.0, 0.0); + _name = "Generic"; + _parent = -1; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoLayer:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _number = lin->get_be_uint16(); + _flags = lin->get_be_uint16(); + _pivot = lin->get_vec3(); + _name = lin->get_string(); + + if (lin->get_bytes_read() >= stop_at) { + _parent = -1; + } else { + _parent = lin->get_be_uint16(); + if (_parent == 0xffff) { + _parent = -1; + } + } + + return true; +} + +/** + * + */ +void LwoLayer:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { number = " << _number << ", flags = 0x" + << std::hex << _flags << std::dec << ", pivot = " << _pivot + << ", _name = \"" << _name << "\", _parent = " << _parent << " }\n"; +} diff --git a/pandatool/src/lwo/lwoLayer.h b/pandatool/src/lwo/lwoLayer.h new file mode 100644 index 00000000..a959d489 --- /dev/null +++ b/pandatool/src/lwo/lwoLayer.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoLayer.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOLAYER_H +#define LWOLAYER_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" + +/** + * Signals the start of a new layer. All the data chunks which follow will be + * included in this layer until another layer chunk is encountered. If data + * is encountered before a layer chunk, it goes into an arbitrary layer. + */ +class LwoLayer : public LwoChunk { +public: + void make_generic(); + + enum Flags { + F_hidden = 0x0001 + }; + + int _number; + int _flags; + LPoint3 _pivot; + std::string _name; + int _parent; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoLayer", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoPoints.cxx b/pandatool/src/lwo/lwoPoints.cxx new file mode 100644 index 00000000..7abfeea6 --- /dev/null +++ b/pandatool/src/lwo/lwoPoints.cxx @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPoints.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoPoints.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoPoints::_type_handle; + +/** + * Returns the number of points of this group. + */ +int LwoPoints:: +get_num_points() const { + return _points.size(); +} + +/** + * Returns the nth point of this group. + */ +const LPoint3 &LwoPoints:: +get_point(int n) const { + nassertr(n >= 0 && n < (int)_points.size(), LPoint3::zero()); + return _points[n]; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoPoints:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + LPoint3 point = lin->get_vec3(); + _points.push_back(point); + } + + return (lin->get_bytes_read() == stop_at); +} + +/** + * + */ +void LwoPoints:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { " << _points.size() << " points }\n"; +} diff --git a/pandatool/src/lwo/lwoPoints.h b/pandatool/src/lwo/lwoPoints.h new file mode 100644 index 00000000..27f2a36c --- /dev/null +++ b/pandatool/src/lwo/lwoPoints.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPoints.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOPOINTS_H +#define LWOPOINTS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" + +/** + * An array of points that will be referenced by later chunks. + */ +class LwoPoints : public LwoChunk { +public: + int get_num_points() const; + const LPoint3 &get_point(int n) const; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef pvector Points; + Points _points; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoPoints", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoPolygonTags.cxx b/pandatool/src/lwo/lwoPolygonTags.cxx new file mode 100644 index 00000000..48f81b47 --- /dev/null +++ b/pandatool/src/lwo/lwoPolygonTags.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPolygonTags.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoPolygonTags.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoPolygonTags::_type_handle; + + +/** + * Returns true if the map has a tag associated with the given polygon index, + * false otherwise. + */ +bool LwoPolygonTags:: +has_tag(int polygon_index) const { + return (_tmap.count(polygon_index) != 0); +} + +/** + * Returns the tag associated with the given polygon index, or -1 if there is + * no tag associated. + */ +int LwoPolygonTags:: +get_tag(int polygon_index) const { + TMap::const_iterator ti; + ti = _tmap.find(polygon_index); + if (ti != _tmap.end()) { + return (*ti).second; + } + + return -1; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoPolygonTags:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _tag_type = lin->get_id(); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + int polygon_index = lin->get_vx(); + int tag = lin->get_be_int16(); + + bool inserted = _tmap.insert(TMap::value_type(polygon_index, tag)).second; + if (!inserted) { + nout << "Duplicate index " << polygon_index << " in map.\n"; + } + } + + return (lin->get_bytes_read() == stop_at); +} + +/** + * + */ +void LwoPolygonTags:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { tag_type = " << _tag_type << ", " + << _tmap.size() << " values }\n"; +} diff --git a/pandatool/src/lwo/lwoPolygonTags.h b/pandatool/src/lwo/lwoPolygonTags.h new file mode 100644 index 00000000..8603492c --- /dev/null +++ b/pandatool/src/lwo/lwoPolygonTags.h @@ -0,0 +1,59 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPolygonTags.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOPOLYGONTAGS_H +#define LWOPOLYGONTAGS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * An association of polygons defined in the most recent LwoPolygons chunk to + * tag ids defined in the most recent LwoTags chunk. This associated + * properties with the polygons, depending on the tag_type. + */ +class LwoPolygonTags : public LwoChunk { +public: + bool has_tag(int polygon_index) const; + int get_tag(int polygon_index) const; + + IffId _tag_type; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef pmap TMap; + TMap _tmap; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoPolygonTags", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoPolygons.cxx b/pandatool/src/lwo/lwoPolygons.cxx new file mode 100644 index 00000000..22d03c21 --- /dev/null +++ b/pandatool/src/lwo/lwoPolygons.cxx @@ -0,0 +1,120 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPolygons.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoPolygons.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoPolygons::_type_handle; + +/** + * Returns the number of polygons of this group. + */ +int LwoPolygons:: +get_num_polygons() const { + return _polygons.size(); +} + +/** + * Returns the nth polygon of this group. + */ +LwoPolygons::Polygon *LwoPolygons:: +get_polygon(int n) const { + nassertr(n >= 0 && n < (int)_polygons.size(), nullptr); + return _polygons[n]; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoPolygons:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + if (lin->get_lwo_version() >= 6.0) { + // 6.x style syntax: POLS { type[ID4], ( numvert+flags[U2], vert[VX] # + // numvert )* } + + _polygon_type = lin->get_id(); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + int nf = lin->get_be_uint16(); + int num_vertices = nf & PF_numverts_mask; + + PT(Polygon) poly = new Polygon; + poly->_flags = nf & ~PF_numverts_mask; + poly->_surface_index = -1; + + for (int i = 0; i < num_vertices; i++) { + int vindex = lin->get_vx(); + poly->_vertices.push_back(vindex); + } + + _polygons.push_back(poly); + } + + } else { + // 5.x style syntax: POLS { ( numvert[U2], vert[VX] # numvert, + // +-(surf+1)[I2], numdetail[U2]? )* } + _polygon_type = IffId("FACE"); + + int num_decals = 0; + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + int num_vertices = lin->get_be_uint16(); + + PT(Polygon) poly = new Polygon; + poly->_flags = 0; + + for (int i = 0; i < num_vertices; i++) { + int vindex = lin->get_vx(); + poly->_vertices.push_back(vindex); + } + + int surface = lin->get_be_int16(); + + if (num_decals > 0) { + // This is a decal polygon of a previous polygon. + num_decals--; + poly->_flags |= PF_decal; + + } else { + if (surface < 0) { + num_decals = lin->get_be_int16(); + surface = -surface; + } + } + + // The surface index is stored +1 to allow signedness to be examined. + poly->_surface_index = surface - 1; + + _polygons.push_back(poly); + } + } + + return true; +} + +/** + * + */ +void LwoPolygons:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { polygon_type = " << _polygon_type + << ", " << _polygons.size() << " polygons }\n"; +} diff --git a/pandatool/src/lwo/lwoPolygons.h b/pandatool/src/lwo/lwoPolygons.h new file mode 100644 index 00000000..d7ad6556 --- /dev/null +++ b/pandatool/src/lwo/lwoPolygons.h @@ -0,0 +1,83 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoPolygons.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOPOLYGONS_H +#define LWOPOLYGONS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" +#include "vector_int.h" +#include "referenceCount.h" +#include "pointerTo.h" + +/** + * An array of polygons that will be referenced by later chunks. + */ +class LwoPolygons : public LwoChunk { +public: + enum PolygonFlags { + PF_continuity_1 = 0x0400, + PF_continuity_2 = 0x0800, + PF_numverts_mask = 0x03f, + + // This "flag" is stored artificially when reading 5.x LWOB files, and + // indicates that the polygon is a decal of a preceding polygon. + PF_decal = 0x0001 + }; + + class Polygon : public ReferenceCount { + public: + int _flags; + vector_int _vertices; + + // This value is only filled in when reading 5.x LWOB files, and indicates + // the surface index of the polygon within a preceding SRFS (LwoTags) + // chunk. For 6.x and later files, this will be set to -1. + int _surface_index; + }; + + int get_num_polygons() const; + Polygon *get_polygon(int n) const; + + IffId _polygon_type; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef pvector< PT(Polygon) > Polygons; + Polygons _polygons; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoPolygons", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoStillImage.cxx b/pandatool/src/lwo/lwoStillImage.cxx new file mode 100644 index 00000000..9a31986c --- /dev/null +++ b/pandatool/src/lwo/lwoStillImage.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoStillImage.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoStillImage.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoStillImage::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoStillImage:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _filename = lin->get_filename(); + + return true; +} + +/** + * + */ +void LwoStillImage:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { filename = \"" << _filename << "\" }\n"; +} diff --git a/pandatool/src/lwo/lwoStillImage.h b/pandatool/src/lwo/lwoStillImage.h new file mode 100644 index 00000000..c800cfb0 --- /dev/null +++ b/pandatool/src/lwo/lwoStillImage.h @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoStillImage.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSTILLIMAGE_H +#define LWOSTILLIMAGE_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "filename.h" + +/** + * A single still image associated with a LwoClip chunk. + */ +class LwoStillImage : public LwoChunk { +public: + Filename _filename; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoStillImage", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurface.cxx b/pandatool/src/lwo/lwoSurface.cxx new file mode 100644 index 00000000..e1318947 --- /dev/null +++ b/pandatool/src/lwo/lwoSurface.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurface.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurface.h" +#include "iffInputFile.h" +#include "lwoSurfaceBlock.h" +#include "lwoSurfaceColor.h" +#include "lwoSurfaceParameter.h" +#include "lwoSurfaceSidedness.h" +#include "lwoSurfaceSmoothingAngle.h" + +#include "indent.h" + +TypeHandle LwoSurface::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurface:: +read_iff(IffInputFile *in, size_t stop_at) { + _name = in->get_string(); + _source = in->get_string(); + read_subchunks_iff(in, stop_at); + return true; +} + +/** + * + */ +void LwoSurface:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + indent(out, indent_level + 2) + << "name = \"" << _name << "\", source = \"" << _source << "\"\n"; + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *LwoSurface:: +make_new_chunk(IffInputFile *in, IffId id) { + if (id == IffId("COLR")) { + return new LwoSurfaceColor; + + } else if (id == IffId("DIFF") || + id == IffId("LUMI") || + id == IffId("SPEC") || + id == IffId("REFL") || + id == IffId("TRAN") || + id == IffId("TRNL") || + id == IffId("GLOS") || + id == IffId("SHRP") || + id == IffId("BUMP") || + id == IffId("RSAN") || + id == IffId("RIND")) { + return new LwoSurfaceParameter; + + } else if (id == IffId("SIDE")) { + return new LwoSurfaceSidedness; + + } else if (id == IffId("SMAN")) { + return new LwoSurfaceSmoothingAngle; + + } else if (id == IffId("BLOK")) { + return new LwoSurfaceBlock; + + } else { + return IffChunk::make_new_chunk(in, id); + } +} diff --git a/pandatool/src/lwo/lwoSurface.h b/pandatool/src/lwo/lwoSurface.h new file mode 100644 index 00000000..75523f0c --- /dev/null +++ b/pandatool/src/lwo/lwoSurface.h @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurface.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACE_H +#define LWOSURFACE_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" + +/** + * Describes the shading attributes of a surface. This is similar to the + * concept usually called a "material" or "shader" in other file formats. + */ +class LwoSurface : public LwoGroupChunk { +public: + std::string _name; + std::string _source; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoGroupChunk::init_type(); + register_type(_type_handle, "LwoSurface", + LwoGroupChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlock.cxx b/pandatool/src/lwo/lwoSurfaceBlock.cxx new file mode 100644 index 00000000..8dd32652 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlock.cxx @@ -0,0 +1,104 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlock.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlock.h" +#include "iffInputFile.h" +#include "lwoSurfaceBlockAxis.h" +#include "lwoSurfaceBlockImage.h" +#include "lwoSurfaceBlockHeader.h" +#include "lwoSurfaceBlockProjection.h" +#include "lwoSurfaceBlockRepeat.h" +#include "lwoSurfaceBlockTMap.h" +#include "lwoSurfaceBlockWrap.h" +#include "lwoSurfaceBlockVMapName.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlock::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlock:: +read_iff(IffInputFile *in, size_t stop_at) { + PT(IffChunk) chunk = in->get_subchunk(this); + if (chunk == nullptr) { + return false; + } + if (!chunk->is_of_type(LwoSurfaceBlockHeader::get_class_type())) { + nout << "Invalid chunk for header of surface block: " << *chunk << "\n"; + return false; + } + + _header = DCAST(LwoSurfaceBlockHeader, chunk); + + read_subchunks_iff(in, stop_at); + return true; +} + +/** + * + */ +void LwoSurfaceBlock:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + _header->write(out, indent_level + 2); + out << "\n"; + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *LwoSurfaceBlock:: +make_new_chunk(IffInputFile *in, IffId id) { + if (id == IffId("IMAP") || + id == IffId("PROC") || + id == IffId("GRAD") || + id == IffId("SHDR")) { + return new LwoSurfaceBlockHeader; + + } else if (id == IffId("TMAP")) { + return new LwoSurfaceBlockTMap; + + } else if (id == IffId("PROJ")) { + return new LwoSurfaceBlockProjection; + + } else if (id == IffId("AXIS")) { + return new LwoSurfaceBlockAxis; + + } else if (id == IffId("IMAG")) { + return new LwoSurfaceBlockImage; + + } else if (id == IffId("WRAP")) { + return new LwoSurfaceBlockWrap; + + } else if (id == IffId("WRPH") || + id == IffId("WRPW")) { + return new LwoSurfaceBlockRepeat; + + } else if (id == IffId("VMAP")) { + return new LwoSurfaceBlockVMapName; + + } else { + return IffChunk::make_new_chunk(in, id); + } +} diff --git a/pandatool/src/lwo/lwoSurfaceBlock.h b/pandatool/src/lwo/lwoSurfaceBlock.h new file mode 100644 index 00000000..bfac032a --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlock.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlock.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCK_H +#define LWOSURFACEBLOCK_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" +#include "lwoSurfaceBlockHeader.h" + +/** + * A texture layer or shader, part of a LwoSurface chunk. + */ +class LwoSurfaceBlock : public LwoGroupChunk { +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + + PT(LwoSurfaceBlockHeader) _header; + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockAxis.cxx b/pandatool/src/lwo/lwoSurfaceBlockAxis.cxx new file mode 100644 index 00000000..3390debd --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockAxis.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockAxis.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockAxis.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockAxis::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockAxis:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _axis = (Axis)lin->get_be_uint16(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockAxis:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { axis = " << (int)_axis << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockAxis.h b/pandatool/src/lwo/lwoSurfaceBlockAxis.h new file mode 100644 index 00000000..4e0920c5 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockAxis.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockAxis.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKAXIS_H +#define LWOSURFACEBLOCKAXIS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates the axis for this particular shader's projection. This works in + * conjunction with LwoSurfaceBlockProjection. This is a subchunk of + * LwoSurfaceBlock. + */ +class LwoSurfaceBlockAxis : public LwoChunk { +public: + enum Axis { + A_x = 0, + A_y = 1, + A_z = 2 + }; + Axis _axis; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockAxis", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockChannel.cxx b/pandatool/src/lwo/lwoSurfaceBlockChannel.cxx new file mode 100644 index 00000000..94dc45c8 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockChannel.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockChannel.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockChannel.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockChannel::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockChannel:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _channel_id = lin->get_id(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockChannel:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { channel_id = " << _channel_id << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockChannel.h b/pandatool/src/lwo/lwoSurfaceBlockChannel.h new file mode 100644 index 00000000..00e35bc2 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockChannel.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockChannel.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKCHANNEL_H +#define LWOSURFACEBLOCKCHANNEL_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates which channel the texture in this LwoSurfaceBlock is applied to. + * This is a subchunk of LwoSurfaceBlockHeader. + */ +class LwoSurfaceBlockChannel : public LwoChunk { +public: + IffId _channel_id; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockChannel", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockCoordSys.cxx b/pandatool/src/lwo/lwoSurfaceBlockCoordSys.cxx new file mode 100644 index 00000000..e2c9b77a --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockCoordSys.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockCoordSys.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockCoordSys.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockCoordSys::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockCoordSys:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _type = (Type)lin->get_be_uint16(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockCoordSys:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { type = " << (int)_type << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockCoordSys.h b/pandatool/src/lwo/lwoSurfaceBlockCoordSys.h new file mode 100644 index 00000000..a9be9419 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockCoordSys.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockCoordSys.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKCOORDSYS_H +#define LWOSURFACEBLOCKCOORDSYS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Specifies whether texture coordinates are computed based on the vertices' + * world coordinates or local coordinates. + */ +class LwoSurfaceBlockCoordSys : public LwoChunk { +public: + enum Type { + T_object = 0, + T_world = 1 + }; + + Type _type; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockCoordSys", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockEnabled.cxx b/pandatool/src/lwo/lwoSurfaceBlockEnabled.cxx new file mode 100644 index 00000000..82405395 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockEnabled.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockEnabled.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockEnabled.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockEnabled::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockEnabled:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _enabled = (lin->get_be_uint16() != 0); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockEnabled:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { enabled = " << _enabled << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockEnabled.h b/pandatool/src/lwo/lwoSurfaceBlockEnabled.h new file mode 100644 index 00000000..a07c6ea3 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockEnabled.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockEnabled.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKENABLED_H +#define LWOSURFACEBLOCKENABLED_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates whether this particular layer or shader should be rendered or + * not. This is a subchunk of LwoSurfaceBlockHeader. + */ +class LwoSurfaceBlockEnabled : public LwoChunk { +public: + bool _enabled; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockEnabled", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockHeader.cxx b/pandatool/src/lwo/lwoSurfaceBlockHeader.cxx new file mode 100644 index 00000000..69351fa4 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockHeader.cxx @@ -0,0 +1,85 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockHeader.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockHeader.h" +#include "lwoInputFile.h" +#include "lwoSurfaceBlockChannel.h" +#include "lwoSurfaceBlockEnabled.h" +#include "lwoSurfaceBlockOpacity.h" +#include "lwoSurfaceBlockAxis.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockHeader::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockHeader:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _ordinal = lin->get_string(); + read_subchunks_iff(lin, stop_at); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockHeader:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + indent(out, indent_level + 2) + << "ordinal = 0x" << std::hex << std::setfill('0'); + + std::string::const_iterator si; + for (si = _ordinal.begin(); si != _ordinal.end(); ++si) { + out << std::setw(2) << (int)(unsigned char)(*si); + } + + out << std::dec << std::setfill(' ') << "\n"; + + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *LwoSurfaceBlockHeader:: +make_new_chunk(IffInputFile *in, IffId id) { + if (id == IffId("CHAN")) { + return new LwoSurfaceBlockChannel; + + } else if (id == IffId("ENAB")) { + return new LwoSurfaceBlockEnabled; + + } else if (id == IffId("OPAC")) { + return new LwoSurfaceBlockOpacity; + + } else if (id == IffId("AXIS")) { + return new LwoSurfaceBlockAxis; + + } else { + return IffChunk::make_new_chunk(in, id); + } +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockHeader.h b/pandatool/src/lwo/lwoSurfaceBlockHeader.h new file mode 100644 index 00000000..6249db26 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockHeader.h @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockHeader.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKHEADER_H +#define LWOSURFACEBLOCKHEADER_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" + +/** + * The header chunk within a LwoSurfaceBlock chunk. + */ +class LwoSurfaceBlockHeader : public LwoGroupChunk { +public: + std::string _ordinal; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoGroupChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockHeader", + LwoGroupChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockImage.cxx b/pandatool/src/lwo/lwoSurfaceBlockImage.cxx new file mode 100644 index 00000000..520d8cef --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockImage.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockImage.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockImage.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockImage::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockImage:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _index = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockImage:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { index = " << _index << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockImage.h b/pandatool/src/lwo/lwoSurfaceBlockImage.h new file mode 100644 index 00000000..968f5d3a --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockImage.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockImage.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKIMAGE_H +#define LWOSURFACEBLOCKIMAGE_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Specifies the particular image that is being applied as a texture. This + * references a recently-defined CLIP image by index number. + */ +class LwoSurfaceBlockImage : public LwoChunk { +public: + int _index; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockImage", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockOpacity.cxx b/pandatool/src/lwo/lwoSurfaceBlockOpacity.cxx new file mode 100644 index 00000000..456e1383 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockOpacity.cxx @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockOpacity.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockOpacity.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockOpacity::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockOpacity:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _type = (Type)lin->get_be_uint16(); + _opacity = lin->get_be_float32(); + _envelope = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockOpacity:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { type = " << (int)_type + << ", opacity = " << _opacity * 100.0 << "%, envelope = " << _envelope + << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockOpacity.h b/pandatool/src/lwo/lwoSurfaceBlockOpacity.h new file mode 100644 index 00000000..906c8615 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockOpacity.h @@ -0,0 +1,63 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockOpacity.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKOPACITY_H +#define LWOSURFACEBLOCKOPACITY_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates how transparent or opaque this particular layer is in relation to + * the layers beneath it. This is a subchunk of LwoSurfaceBlockHeader. + */ +class LwoSurfaceBlockOpacity : public LwoChunk { +public: + enum Type { + T_additive = 0, + T_subtractive = 1, + T_difference = 2, + T_multiply = 3, + T_divide = 4, + T_alpha = 5, + T_texture_displacement = 6 + }; + + Type _type; + PN_stdfloat _opacity; + int _envelope; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockOpacity", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockProjection.cxx b/pandatool/src/lwo/lwoSurfaceBlockProjection.cxx new file mode 100644 index 00000000..500d90c2 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockProjection.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockProjection.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockProjection.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockProjection::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockProjection:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _mode = (Mode)lin->get_be_uint16(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockProjection:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { mode = " << (int)_mode << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockProjection.h b/pandatool/src/lwo/lwoSurfaceBlockProjection.h new file mode 100644 index 00000000..bcbbff41 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockProjection.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockProjection.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKPROJECTION_H +#define LWOSURFACEBLOCKPROJECTION_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates the projection mode for this particular shader. This determines + * how UV coordinates should be computed based on the vertex positions. This + * is a subchunk of LwoSurfaceBlock. + */ +class LwoSurfaceBlockProjection : public LwoChunk { +public: + enum Mode { + M_planar = 0, + M_cylindrical = 1, + M_spherical = 2, + M_cubic = 3, + M_front = 4, + M_uv = 5 + }; + Mode _mode; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockProjection", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockRefObj.cxx b/pandatool/src/lwo/lwoSurfaceBlockRefObj.cxx new file mode 100644 index 00000000..1c19fedf --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockRefObj.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockRefObj.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockRefObj.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockRefObj::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockRefObj:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _name = lin->get_string(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockRefObj:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { name = \"" << _name << "\" }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockRefObj.h b/pandatool/src/lwo/lwoSurfaceBlockRefObj.h new file mode 100644 index 00000000..77818957 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockRefObj.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockRefObj.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKREFOBJ_H +#define LWOSURFACEBLOCKREFOBJ_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Specifies a reference object that the texture UV's are to be computed + * relative to. + */ +class LwoSurfaceBlockRefObj : public LwoChunk { +public: + std::string _name; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockRefObj", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockRepeat.cxx b/pandatool/src/lwo/lwoSurfaceBlockRepeat.cxx new file mode 100644 index 00000000..9036c143 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockRepeat.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockRepeat.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockRepeat.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockRepeat::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockRepeat:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _cycles = lin->get_be_float32(); + _envelope = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockRepeat:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { cycles = " << _cycles + << ", envelope = " << _envelope << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockRepeat.h b/pandatool/src/lwo/lwoSurfaceBlockRepeat.h new file mode 100644 index 00000000..73c77a45 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockRepeat.h @@ -0,0 +1,54 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockRepeat.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKREPEAT_H +#define LWOSURFACEBLOCKREPEAT_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * For cylindrical and spherical projections, this parameter controls how many + * times the image repeats over each full interval, in either dimension. The + * dimension is specified by the id of the chunk, either WRPW or WRPH. This + * is a subchunk of LwoSurfaceBlock. + */ +class LwoSurfaceBlockRepeat : public LwoChunk { +public: + PN_stdfloat _cycles; + int _envelope; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockRepeat", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockTMap.cxx b/pandatool/src/lwo/lwoSurfaceBlockTMap.cxx new file mode 100644 index 00000000..1f355d2d --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockTMap.cxx @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockTMap.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockTMap.h" +#include "lwoInputFile.h" +#include "lwoSurfaceBlockCoordSys.h" +#include "lwoSurfaceBlockTransform.h" +#include "lwoSurfaceBlockRefObj.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockTMap::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockTMap:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + read_subchunks_iff(lin, stop_at); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockTMap:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " {\n"; + write_chunks(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Allocates and returns a new chunk of the appropriate type based on the + * given ID, according to the context given by this chunk itself. + */ +IffChunk *LwoSurfaceBlockTMap:: +make_new_chunk(IffInputFile *in, IffId id) { + if (id == IffId("CNTR") || + id == IffId("SIZE") || + id == IffId("ROTA")) { + return new LwoSurfaceBlockTransform; + + } else if (id == IffId("OREF")) { + return new LwoSurfaceBlockRefObj; + + } else if (id == IffId("CSYS")) { + return new LwoSurfaceBlockCoordSys; + + } else { + return IffChunk::make_new_chunk(in, id); + } +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockTMap.h b/pandatool/src/lwo/lwoSurfaceBlockTMap.h new file mode 100644 index 00000000..273522d5 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockTMap.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockTMap.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKTMAP_H +#define LWOSURFACEBLOCKTMAP_H + +#include "pandatoolbase.h" + +#include "lwoGroupChunk.h" + +/** + * The tMap chunk within a LwoSurfaceBlock chunk. + */ +class LwoSurfaceBlockTMap : public LwoGroupChunk { +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + + virtual IffChunk *make_new_chunk(IffInputFile *in, IffId id); + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoGroupChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockTMap", + LwoGroupChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockTransform.cxx b/pandatool/src/lwo/lwoSurfaceBlockTransform.cxx new file mode 100644 index 00000000..2ed27381 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockTransform.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockTransform.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockTransform.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockTransform::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockTransform:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _vec = lin->get_vec3(); + _envelope = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockTransform:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { vec = " << _vec + << ", envelope = " << _envelope << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockTransform.h b/pandatool/src/lwo/lwoSurfaceBlockTransform.h new file mode 100644 index 00000000..e3e07a49 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockTransform.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockTransform.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKTRANSFORM_H +#define LWOSURFACEBLOCKTRANSFORM_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" + +/** + * Specifies a center point, scale, or rotation for the texture coordinates in + * this shader's texture mapping. The type of transform is specified by the + * ID of the chunk; either CNTR, SIZE, or ROTA. This is a subchunk of + * LwoSurfaceBlockTMap. + */ +class LwoSurfaceBlockTransform : public LwoChunk { +public: + LVecBase3 _vec; + int _envelope; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockTransform", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockVMapName.cxx b/pandatool/src/lwo/lwoSurfaceBlockVMapName.cxx new file mode 100644 index 00000000..f4db6994 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockVMapName.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockVMapName.cxx + * @author drose + * @date 2001-04-30 + */ + +#include "lwoSurfaceBlockVMapName.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockVMapName::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockVMapName:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _name = lin->get_string(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockVMapName:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { name = \"" << _name << "\" }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockVMapName.h b/pandatool/src/lwo/lwoSurfaceBlockVMapName.h new file mode 100644 index 00000000..1433bfad --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockVMapName.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockVMapName.h + * @author drose + * @date 2001-04-30 + */ + +#ifndef LWOSURFACEBLOCKVMAPNAME_H +#define LWOSURFACEBLOCKVMAPNAME_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Specifies the name of a set of UV's defined on the polygons that use this + * model. + */ +class LwoSurfaceBlockVMapName : public LwoChunk { +public: + std::string _name; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockVMapName", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceBlockWrap.cxx b/pandatool/src/lwo/lwoSurfaceBlockWrap.cxx new file mode 100644 index 00000000..006be5f8 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockWrap.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockWrap.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceBlockWrap.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceBlockWrap::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceBlockWrap:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _width = (Mode)lin->get_be_uint16(); + _height = (Mode)lin->get_be_uint16(); + + return true; +} + +/** + * + */ +void LwoSurfaceBlockWrap:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { width = " << (int)_width + << ", height = " << (int)_height << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceBlockWrap.h b/pandatool/src/lwo/lwoSurfaceBlockWrap.h new file mode 100644 index 00000000..bf18e92d --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceBlockWrap.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceBlockWrap.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEBLOCKWRAP_H +#define LWOSURFACEBLOCKWRAP_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Specifies how the texture image appears for areas outside the image. + */ +class LwoSurfaceBlockWrap : public LwoChunk { +public: + enum Mode { + M_reset = 0, // black outside + M_repeat = 1, // standard repeat + M_mirror = 2, // repeat with reflection + M_edge = 3 // GL-style clamping + }; + Mode _width, _height; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceBlockWrap", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceColor.cxx b/pandatool/src/lwo/lwoSurfaceColor.cxx new file mode 100644 index 00000000..5a4526eb --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceColor.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceColor.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceColor.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceColor::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceColor:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _color = lin->get_vec3(); + _envelope = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceColor:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { color = " << _color + << ", envelope = " << _envelope << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceColor.h b/pandatool/src/lwo/lwoSurfaceColor.h new file mode 100644 index 00000000..584d1e78 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceColor.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceColor.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACECOLOR_H +#define LWOSURFACECOLOR_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" + +/** + * Records the base color of a surface, as an entry within a LwoSurface chunk. + */ +class LwoSurfaceColor : public LwoChunk { +public: + LRGBColor _color; + int _envelope; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceColor", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceParameter.cxx b/pandatool/src/lwo/lwoSurfaceParameter.cxx new file mode 100644 index 00000000..fd3543f1 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceParameter.cxx @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceParameter.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceParameter.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceParameter::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceParameter:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _value = lin->get_be_float32(); + _envelope = lin->get_vx(); + + return true; +} + +/** + * + */ +void LwoSurfaceParameter:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { value = " << _value + << ", envelope = " << _envelope << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceParameter.h b/pandatool/src/lwo/lwoSurfaceParameter.h new file mode 100644 index 00000000..075383b3 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceParameter.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceParameter.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACEPARAMETER_H +#define LWOSURFACEPARAMETER_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Records some parameter value of a surface material, as an entry within a + * LwoSurface chunk. The meaning of the value is determined by the id of this + * chunk. + */ +class LwoSurfaceParameter : public LwoChunk { +public: + PN_stdfloat _value; + int _envelope; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceParameter", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceSidedness.cxx b/pandatool/src/lwo/lwoSurfaceSidedness.cxx new file mode 100644 index 00000000..2c773b25 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceSidedness.cxx @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceSidedness.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceSidedness.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoSurfaceSidedness::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceSidedness:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _sidedness = (Sidedness)lin->get_be_int16(); + + return true; +} + +/** + * + */ +void LwoSurfaceSidedness:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { sidedness = " << (int)_sidedness << " }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceSidedness.h b/pandatool/src/lwo/lwoSurfaceSidedness.h new file mode 100644 index 00000000..f64ef112 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceSidedness.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceSidedness.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACESIDEDNESS_H +#define LWOSURFACESIDEDNESS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Records whether polygons are frontfacing only or backfacing also. This is + * associated with the LwoSurface chunk. + */ +class LwoSurfaceSidedness : public LwoChunk { +public: + enum Sidedness { + S_front = 1, + S_front_and_back = 3 + }; + + Sidedness _sidedness; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceSidedness", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoSurfaceSmoothingAngle.cxx b/pandatool/src/lwo/lwoSurfaceSmoothingAngle.cxx new file mode 100644 index 00000000..49c1f729 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceSmoothingAngle.cxx @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceSmoothingAngle.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoSurfaceSmoothingAngle.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" +#include "deg_2_rad.h" + +TypeHandle LwoSurfaceSmoothingAngle::_type_handle; + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoSurfaceSmoothingAngle:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _angle = lin->get_be_float32(); + + return true; +} + +/** + * + */ +void LwoSurfaceSmoothingAngle:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { angle = " << rad_2_deg(_angle) << " degrees }\n"; +} diff --git a/pandatool/src/lwo/lwoSurfaceSmoothingAngle.h b/pandatool/src/lwo/lwoSurfaceSmoothingAngle.h new file mode 100644 index 00000000..c27b9a30 --- /dev/null +++ b/pandatool/src/lwo/lwoSurfaceSmoothingAngle.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoSurfaceSmoothingAngle.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOSURFACESMOOTHINGANGLE_H +#define LWOSURFACESMOOTHINGANGLE_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +/** + * Indicates the maximum angle (in radians) between adjacent polygons that + * should be smooth-shaded. + */ +class LwoSurfaceSmoothingAngle : public LwoChunk { +public: + PN_stdfloat _angle; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoSurfaceSmoothingAngle", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoTags.cxx b/pandatool/src/lwo/lwoTags.cxx new file mode 100644 index 00000000..43eaa2ab --- /dev/null +++ b/pandatool/src/lwo/lwoTags.cxx @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoTags.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoTags.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoTags::_type_handle; + +/** + * Returns the number of tags of this group. + */ +int LwoTags:: +get_num_tags() const { + return _tags.size(); +} + +/** + * Returns the nth tag of this group. + */ +std::string LwoTags:: +get_tag(int n) const { + nassertr(n >= 0 && n < (int)_tags.size(), std::string()); + return _tags[n]; +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoTags:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + std::string tag = lin->get_string(); + _tags.push_back(tag); + } + + return (lin->get_bytes_read() == stop_at); +} + +/** + * + */ +void LwoTags:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { "; + + if (!_tags.empty()) { + Tags::const_iterator ti = _tags.begin(); + out << '"' << *ti << '"'; + ++ti; + while (ti != _tags.end()) { + out << ", \"" << *ti << '"'; + ++ti; + } + } + out << " }\n"; +} diff --git a/pandatool/src/lwo/lwoTags.h b/pandatool/src/lwo/lwoTags.h new file mode 100644 index 00000000..03ac6ede --- /dev/null +++ b/pandatool/src/lwo/lwoTags.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoTags.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOTAGS_H +#define LWOTAGS_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "luse.h" +#include "vector_string.h" + +/** + * An array of tag strings that will be referenced by later chunks. + * + * This also serves as an array of surface names to be referenced by a later + * LwoPolygons chunk, in 5.x LWOB files. The chunk id can be used to + * differentiate the meaning (TAGS vs. SRFS). + */ +class LwoTags : public LwoChunk { +public: + int get_num_tags() const; + std::string get_tag(int n) const; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef vector_string Tags; + Tags _tags; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoTags", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/lwoVertexMap.cxx b/pandatool/src/lwo/lwoVertexMap.cxx new file mode 100644 index 00000000..a0ecbf9c --- /dev/null +++ b/pandatool/src/lwo/lwoVertexMap.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoVertexMap.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoVertexMap.h" +#include "lwoInputFile.h" + +#include "dcast.h" +#include "indent.h" + +TypeHandle LwoVertexMap::_type_handle; + + +/** + * Returns true if the map has a value associated with the given index, false + * otherwise. + */ +bool LwoVertexMap:: +has_value(int index) const { + return (_vmap.count(index) != 0); +} + +/** + * Returns the mapping value associated with the given index, or an empty + * PTA_stdfloat if there is no mapping value associated. + */ +PTA_stdfloat LwoVertexMap:: +get_value(int index) const { + VMap::const_iterator vi; + vi = _vmap.find(index); + if (vi != _vmap.end()) { + return (*vi).second; + } + + return PTA_stdfloat(); +} + +/** + * Reads the data of the chunk in from the given input file, if possible. The + * ID and length of the chunk have already been read. stop_at is the byte + * position of the file to stop at (based on the current position at + * in->get_bytes_read()). Returns true on success, false otherwise. + */ +bool LwoVertexMap:: +read_iff(IffInputFile *in, size_t stop_at) { + LwoInputFile *lin = DCAST(LwoInputFile, in); + + _map_type = lin->get_id(); + _dimension = lin->get_be_uint16(); + _name = lin->get_string(); + + while (lin->get_bytes_read() < stop_at && !lin->is_eof()) { + int index = lin->get_vx(); + + PTA_stdfloat value; + for (int i = 0; i < _dimension; i++) { + value.push_back(lin->get_be_float32()); + } + + bool inserted = _vmap.insert(VMap::value_type(index, value)).second; + if (!inserted) { + nout << "Duplicate index " << index << " in map.\n"; + } + } + + return (lin->get_bytes_read() == stop_at); +} + +/** + * + */ +void LwoVertexMap:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_id() << " { map_type = " << _map_type + << ", dimension = " << _dimension + << ", name = \"" << _name << "\", " + << _vmap.size() << " values }\n"; +} diff --git a/pandatool/src/lwo/lwoVertexMap.h b/pandatool/src/lwo/lwoVertexMap.h new file mode 100644 index 00000000..e2b05906 --- /dev/null +++ b/pandatool/src/lwo/lwoVertexMap.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoVertexMap.h + * @author drose + * @date 2001-04-24 + */ + +#ifndef LWOVERTEXMAP_H +#define LWOVERTEXMAP_H + +#include "pandatoolbase.h" + +#include "lwoChunk.h" + +#include "pta_stdfloat.h" + +/** + * A mapping of floating-point values per integer index. The meaning of these + * values is determined by the mapping type code and/or its name. + */ +class LwoVertexMap : public LwoChunk { +public: + bool has_value(int index) const; + PTA_stdfloat get_value(int index) const; + + IffId _map_type; + int _dimension; + std::string _name; + +public: + virtual bool read_iff(IffInputFile *in, size_t stop_at); + virtual void write(std::ostream &out, int indent_level = 0) const; + +private: + typedef pmap VMap; + VMap _vmap; + +public: + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LwoChunk::init_type(); + register_type(_type_handle, "LwoVertexMap", + LwoChunk::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/lwo/p3lwo_composite1.cxx b/pandatool/src/lwo/p3lwo_composite1.cxx new file mode 100644 index 00000000..e3589ed7 --- /dev/null +++ b/pandatool/src/lwo/p3lwo_composite1.cxx @@ -0,0 +1,41 @@ + +#include "config_lwo.cxx" +#include "iffChunk.cxx" +#include "iffGenericChunk.cxx" +#include "iffId.cxx" +#include "iffInputFile.cxx" +#include "lwoBoundingBox.cxx" +#include "lwoChunk.cxx" +#include "lwoClip.cxx" +#include "lwoDiscontinuousVertexMap.cxx" +#include "lwoGroupChunk.cxx" +#include "lwoHeader.cxx" +#include "lwoInputFile.cxx" +#include "lwoLayer.cxx" +#include "lwoPoints.cxx" +#include "lwoPolygons.cxx" +#include "lwoPolygonTags.cxx" +#include "lwoTags.cxx" +#include "lwoStillImage.cxx" +#include "lwoSurface.cxx" +#include "lwoSurfaceBlock.cxx" +#include "lwoSurfaceBlockAxis.cxx" +#include "lwoSurfaceBlockChannel.cxx" +#include "lwoSurfaceBlockCoordSys.cxx" +#include "lwoSurfaceBlockEnabled.cxx" +#include "lwoSurfaceBlockImage.cxx" +#include "lwoSurfaceBlockOpacity.cxx" +#include "lwoSurfaceBlockProjection.cxx" +#include "lwoSurfaceBlockHeader.cxx" +#include "lwoSurfaceBlockRefObj.cxx" +#include "lwoSurfaceBlockRepeat.cxx" +#include "lwoSurfaceBlockTMap.cxx" +#include "lwoSurfaceBlockTransform.cxx" +#include "lwoSurfaceBlockVMapName.cxx" +#include "lwoSurfaceBlockWrap.cxx" +#include "lwoSurfaceColor.cxx" +#include "lwoSurfaceParameter.cxx" +#include "lwoSurfaceSidedness.cxx" +#include "lwoSurfaceSmoothingAngle.cxx" +#include "lwoVertexMap.cxx" + diff --git a/pandatool/src/lwo/test_lwo.cxx b/pandatool/src/lwo/test_lwo.cxx new file mode 100644 index 00000000..baba5984 --- /dev/null +++ b/pandatool/src/lwo/test_lwo.cxx @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file test_lwo.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoInputFile.h" +#include "lwoChunk.h" +#include "config_lwo.h" + +int +main(int argc, char *argv[]) { + init_liblwo(); + if (argc != 2) { + nout << "test_lwo file.lwo\n"; + exit(1); + } + + LwoInputFile in; + if (!in.open_read(argv[1])) { + nout << "Unable to open " << argv[1] << "\n"; + exit(1); + } + + PT(IffChunk) chunk = in.get_chunk(); + while (chunk != nullptr) { + nout << "Got chunk type " << chunk->get_type() << ":\n"; + chunk->write(nout, 2); + chunk = in.get_chunk(); + } + + nout << "EOF = " << in.is_eof() << "\n"; + + return (0); +} diff --git a/pandatool/src/lwoegg/CMakeLists.txt b/pandatool/src/lwoegg/CMakeLists.txt new file mode 100644 index 00000000..dbd4cff0 --- /dev/null +++ b/pandatool/src/lwoegg/CMakeLists.txt @@ -0,0 +1,31 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3LWOEGG_HEADERS + cLwoClip.h cLwoClip.I + cLwoLayer.h cLwoLayer.I + cLwoPoints.h cLwoPoints.I + cLwoPolygons.h cLwoPolygons.I + cLwoSurfaceBlock.h cLwoSurfaceBlock.I + cLwoSurfaceBlockTMap.h cLwoSurfaceBlockTMap.I + cLwoSurface.h cLwoSurface.I + lwoToEggConverter.h lwoToEggConverter.I +) + +set(P3LWOEGG_SOURCES + cLwoClip.cxx + cLwoLayer.cxx + cLwoPoints.cxx + cLwoPolygons.cxx + cLwoSurfaceBlock.cxx + cLwoSurfaceBlockTMap.cxx + cLwoSurface.cxx + lwoToEggConverter.cxx +) + +add_library(p3lwoegg STATIC ${P3LWOEGG_HEADERS} ${P3LWOEGG_SOURCES}) +target_link_libraries(p3lwoegg p3lwo p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/lwoegg/cLwoClip.I b/pandatool/src/lwoegg/cLwoClip.I new file mode 100644 index 00000000..8b2e4ccb --- /dev/null +++ b/pandatool/src/lwoegg/cLwoClip.I @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoClip.I + * @author drose + * @date 2001-04-27 + */ + +/** + * Returns the index number of this clip. Each clip in a Lightwave object + * file should have a unique index number. + */ +int CLwoClip:: +get_index() const { + return _clip->_index; +} + +/** + * Returns true if this clip represents a still image, as opposed to an + * animated image. If this is true, _filename will contain the image + * filename. + */ +bool CLwoClip:: +is_still_image() const { + return _still_image; +} diff --git a/pandatool/src/lwoegg/cLwoClip.cxx b/pandatool/src/lwoegg/cLwoClip.cxx new file mode 100644 index 00000000..42850133 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoClip.cxx @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoClip.cxx + * @author drose + * @date 2001-04-27 + */ + +#include "cLwoClip.h" +#include "lwoToEggConverter.h" + +#include "lwoClip.h" +#include "lwoStillImage.h" +#include "dcast.h" + + +/** + * + */ +CLwoClip:: +CLwoClip(LwoToEggConverter *converter, const LwoClip *clip) : + _converter(converter), + _clip(clip) +{ + _still_image = false; + + // Walk through the chunk list, looking for some basic properties. + int num_chunks = _clip->get_num_chunks(); + for (int i = 0; i < num_chunks; i++) { + const IffChunk *chunk = _clip->get_chunk(i); + + if (chunk->is_of_type(LwoStillImage::get_class_type())) { + const LwoStillImage *image = DCAST(LwoStillImage, chunk); + _filename = image->_filename; + _still_image = true; + } + } +} diff --git a/pandatool/src/lwoegg/cLwoClip.h b/pandatool/src/lwoegg/cLwoClip.h new file mode 100644 index 00000000..118d883c --- /dev/null +++ b/pandatool/src/lwoegg/cLwoClip.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoClip.h + * @author drose + * @date 2001-04-26 + */ + +#ifndef CLWOCLIP_H +#define CLWOCLIP_H + +#include "pandatoolbase.h" + +#include "lwoClip.h" +#include "eggGroup.h" +#include "pointerTo.h" + +class LwoToEggConverter; + +/** + * This class is a wrapper around LwoClip and stores additional information + * useful during the conversion-to-egg process. + */ +class CLwoClip { +public: + CLwoClip(LwoToEggConverter *converter, const LwoClip *clip); + + INLINE int get_index() const; + INLINE bool is_still_image() const; + + LwoToEggConverter *_converter; + CPT(LwoClip) _clip; + + Filename _filename; + bool _still_image; +}; + +#include "cLwoClip.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoLayer.I b/pandatool/src/lwoegg/cLwoLayer.I new file mode 100644 index 00000000..56f8a4b5 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoLayer.I @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoLayer.I + * @author drose + * @date 2001-04-25 + */ + +/** + * + */ +INLINE CLwoLayer:: +CLwoLayer(LwoToEggConverter *converter, const LwoLayer *layer) : + _converter(converter), + _layer(layer) +{ +} + +/** + * Returns the index number associated with this particular layer. This + * should be unique among all Lightwave layers in a single file. + */ +INLINE int CLwoLayer:: +get_number() const { + return _layer->_number; +} diff --git a/pandatool/src/lwoegg/cLwoLayer.cxx b/pandatool/src/lwoegg/cLwoLayer.cxx new file mode 100644 index 00000000..73562125 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoLayer.cxx @@ -0,0 +1,52 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoLayer.cxx + * @author drose + * @date 2001-04-25 + */ + +#include "cLwoLayer.h" +#include "lwoToEggConverter.h" + +#include "eggData.h" + + +/** + * Creates the egg structures associated with this Lightwave object. + */ +void CLwoLayer:: +make_egg() { + _egg_group = new EggGroup(_layer->_name); + + if (_layer->_pivot != LPoint3::zero()) { + // If we have a nonzero pivot point, that's a translation transform. + LPoint3d translate = LCAST(double, _layer->_pivot); + _egg_group->set_transform3d(LMatrix4d::translate_mat(translate)); + _egg_group->set_group_type(EggGroup::GT_instance); + } +} + +/** + * Connects all the egg structures together. + */ +void CLwoLayer:: +connect_egg() { + if (_layer->_parent != -1) { + const CLwoLayer *parent = _converter->get_layer(_layer->_parent); + if (parent != nullptr) { + parent->_egg_group->add_child(_egg_group.p()); + return; + } + + nout << "No layer found with number " << _layer->_parent + << "; cannot parent layer " << _layer->_number << " properly.\n"; + } + + _converter->get_egg_data()->add_child(_egg_group.p()); +} diff --git a/pandatool/src/lwoegg/cLwoLayer.h b/pandatool/src/lwoegg/cLwoLayer.h new file mode 100644 index 00000000..568654ff --- /dev/null +++ b/pandatool/src/lwoegg/cLwoLayer.h @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoLayer.h + * @author drose + * @date 2001-04-25 + */ + +#ifndef CLWOLAYER_H +#define CLWOLAYER_H + +#include "pandatoolbase.h" + +#include "lwoLayer.h" +#include "eggGroup.h" +#include "pointerTo.h" + +class LwoToEggConverter; + +/** + * This class is a wrapper around LwoLayer and stores additional information + * useful during the conversion-to-egg process. + */ +class CLwoLayer { +public: + INLINE CLwoLayer(LwoToEggConverter *converter, const LwoLayer *layer); + INLINE int get_number() const; + + void make_egg(); + void connect_egg(); + + LwoToEggConverter *_converter; + CPT(LwoLayer) _layer; + PT(EggGroup) _egg_group; +}; + +#include "cLwoLayer.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoPoints.I b/pandatool/src/lwoegg/cLwoPoints.I new file mode 100644 index 00000000..2d785c07 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPoints.I @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPoints.I + * @author drose + * @date 2001-04-25 + */ + +/** + * + */ +INLINE CLwoPoints:: +CLwoPoints(LwoToEggConverter *converter, const LwoPoints *points, + CLwoLayer *layer) : + _converter(converter), + _points(points), + _layer(layer) +{ +} diff --git a/pandatool/src/lwoegg/cLwoPoints.cxx b/pandatool/src/lwoegg/cLwoPoints.cxx new file mode 100644 index 00000000..93a89f79 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPoints.cxx @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPoints.cxx + * @author drose + * @date 2001-04-25 + */ + +#include "cLwoPoints.h" +#include "lwoToEggConverter.h" +#include "cLwoLayer.h" + +#include "pta_stdfloat.h" +#include "lwoVertexMap.h" +#include "string_utils.h" + +/** + * Associates the indicated VertexMap with the points set. This may define + * such niceties as UV coordinates or per-vertex color. + */ +void CLwoPoints:: +add_vmap(const LwoVertexMap *lwo_vmap) { + IffId map_type = lwo_vmap->_map_type; + const std::string &name = lwo_vmap->_name; + + bool inserted; + if (map_type == IffId("TXUV")) { + inserted = + _txuv.insert(VMap::value_type(name, lwo_vmap)).second; + + } else if (map_type == IffId("PICK")) { + inserted = + _pick.insert(VMap::value_type(name, lwo_vmap)).second; + + } else { + return; + } + + if (!inserted) { + nout << "Multiple vertex maps on the same points of type " + << map_type << " named " << name << "\n"; + } +} + +/** + * Returns true if there is a UV of the indicated name associated with the + * given vertex, false otherwise. If true, fills in uv with the value. + */ +bool CLwoPoints:: +get_uv(const std::string &uv_name, int n, LPoint2 &uv) const { + VMap::const_iterator ni = _txuv.find(uv_name); + if (ni == _txuv.end()) { + return false; + } + + const LwoVertexMap *vmap = (*ni).second; + if (vmap->_dimension != 2) { + nout << "Unexpected dimension of " << vmap->_dimension + << " for UV map " << uv_name << "\n"; + return false; + } + + if (!vmap->has_value(n)) { + return false; + } + + PTA_stdfloat value = vmap->get_value(n); + + uv.set(value[0], value[1]); + return true; +} + +/** + * Creates the egg structures associated with this Lightwave object. + */ +void CLwoPoints:: +make_egg() { + // Generate a vpool name based on the layer index, for lack of anything + // better. + std::string vpool_name = "layer" + format_string(_layer->get_number()); + _egg_vpool = new EggVertexPool(vpool_name); +} + +/** + * Connects all the egg structures together. + */ +void CLwoPoints:: +connect_egg() { + if (!_egg_vpool->empty()) { + _layer->_egg_group->add_child(_egg_vpool.p()); + } +} diff --git a/pandatool/src/lwoegg/cLwoPoints.h b/pandatool/src/lwoegg/cLwoPoints.h new file mode 100644 index 00000000..a4dbf642 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPoints.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPoints.h + * @author drose + * @date 2001-04-25 + */ + +#ifndef CLWOPOINTS_H +#define CLWOPOINTS_H + +#include "pandatoolbase.h" + +#include "lwoPoints.h" +#include "eggVertexPool.h" +#include "pointerTo.h" + +#include "pmap.h" + +class LwoToEggConverter; +class LwoVertexMap; +class CLwoLayer; + +/** + * This class is a wrapper around LwoPoints and stores additional information + * useful during the conversion-to-egg process. + */ +class CLwoPoints { +public: + INLINE CLwoPoints(LwoToEggConverter *converter, const LwoPoints *points, + CLwoLayer *layer); + + void add_vmap(const LwoVertexMap *lwo_vmap); + bool get_uv(const std::string &uv_name, int n, LPoint2 &uv) const; + + void make_egg(); + void connect_egg(); + + LwoToEggConverter *_converter; + CPT(LwoPoints) _points; + CLwoLayer *_layer; + PT(EggVertexPool) _egg_vpool; + + // A number of vertex maps of different types may be associated, but we only + // care about some of the types here. + typedef pmap VMap; + VMap _txuv; + VMap _pick; +}; + +#include "cLwoPoints.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoPolygons.I b/pandatool/src/lwoegg/cLwoPolygons.I new file mode 100644 index 00000000..94908deb --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPolygons.I @@ -0,0 +1,26 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPolygons.I + * @author drose + * @date 2001-04-25 + */ + +/** + * + */ +INLINE CLwoPolygons:: +CLwoPolygons(LwoToEggConverter *converter, const LwoPolygons *polygons, + CLwoPoints *points) : + _converter(converter), + _polygons(polygons), + _points(points) +{ + _tags = nullptr; + _surf_ptags = nullptr; +} diff --git a/pandatool/src/lwoegg/cLwoPolygons.cxx b/pandatool/src/lwoegg/cLwoPolygons.cxx new file mode 100644 index 00000000..e6a0fac4 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPolygons.cxx @@ -0,0 +1,284 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPolygons.cxx + * @author drose + * @date 2001-04-25 + */ + +#include "cLwoPolygons.h" +#include "lwoToEggConverter.h" +#include "cLwoPoints.h" +#include "cLwoLayer.h" +#include "cLwoSurface.h" + +#include "lwoPolygonTags.h" +#include "lwoTags.h" +#include "lwoDiscontinuousVertexMap.h" +#include "eggData.h" +#include "eggPolygon.h" +#include "eggPoint.h" +#include "deg_2_rad.h" + +using std::string; + +/** + * Associates the indicated PolygonTags and Tags with the polygons in this + * chunk. This may define features such as per-polygon surfaces, parts, and + * smoothing groups. + */ +void CLwoPolygons:: +add_ptags(const LwoPolygonTags *lwo_ptags, const LwoTags *tags) { + if (_tags != nullptr && _tags != tags) { + nout << "Multiple Tags fields in effect on the same polygons.\n"; + } + _tags = tags; + + IffId type = lwo_ptags->_tag_type; + + bool inserted = _ptags.insert(PTags::value_type(type, lwo_ptags)).second; + if (!inserted) { + nout << "Multiple polygon tags on the same polygons of type " + << type << "\n"; + + } else { + if (type == IffId("SURF")) { + _surf_ptags = lwo_ptags; + } + } +} + +/** + * Associates the indicated DiscontinousVertexMap with the polygons. This can + * be used in conjunction with (or in place of) the VertexMap associated with + * the points set, to define per-polygon UV's etc. + */ +void CLwoPolygons:: +add_vmad(const LwoDiscontinuousVertexMap *lwo_vmad) { + IffId map_type = lwo_vmad->_map_type; + const string &name = lwo_vmad->_name; + + bool inserted; + if (map_type == IffId("TXUV")) { + inserted = + _txuv.insert(VMad::value_type(name, lwo_vmad)).second; + + } else { + return; + } + + if (!inserted) { + nout << "Multiple discontinous vertex maps on the same polygons of type " + << map_type << " named " << name << "\n"; + } +} + +/** + * Returns the surface associated with the given polygon, or NULL if no + * surface is associated. + */ +CLwoSurface *CLwoPolygons:: +get_surface(int polygon_index) const { + if (_surf_ptags == nullptr) { + // No surface definitions. + return nullptr; + } + + if (!_surf_ptags->has_tag(polygon_index)) { + // The polygon isn't tagged. + return nullptr; + } + + int tag_index = _surf_ptags->get_tag(polygon_index); + if (_tags == nullptr || tag_index < 0 || + tag_index >= _tags->get_num_tags()) { + // The tag index is out-of-bounds. + nout << "Invalid polygon tag index " << tag_index << "\n"; + return nullptr; + } + + string tag = _tags->get_tag(tag_index); + + // Now look up the surface name in the header. + CLwoSurface *surface = _converter->get_surface(tag); + if (surface == nullptr) { + nout << "Unknown surface " << tag << "\n"; + return nullptr; + } + + return surface; +} + +/** + * Returns true if there is a UV of the indicated name associated with the + * given vertex of the indicated polygon, false otherwise. If true, fills in + * uv with the value. + * + * This performs a lookup in the optional "discontinuous" vertex mapping, + * which provides the ability to map different UV's per each polygon for the + * same vertex. If the UV is not defined here, it may also be defined in the + * standard vertex map, which is associated with the points themselves. + */ +bool CLwoPolygons:: +get_uv(const string &uv_name, int pi, int vi, LPoint2 &uv) const { + VMad::const_iterator ni = _txuv.find(uv_name); + if (ni == _txuv.end()) { + return false; + } + + const LwoDiscontinuousVertexMap *vmad = (*ni).second; + if (vmad->_dimension != 2) { + nout << "Unexpected dimension of " << vmad->_dimension + << " for discontinuous UV map " << uv_name << "\n"; + return false; + } + + if (!vmad->has_value(pi, vi)) { + return false; + } + + PTA_stdfloat value = vmad->get_value(pi, vi); + + uv.set(value[0], value[1]); + return true; +} + +/** + * Creates the egg structures associated with this Lightwave object. + */ +void CLwoPolygons:: +make_egg() { + // First, we need a temporary group to hold all of the polygons we'll + // create. + _egg_group = new EggGroup; + + if (_polygons->_polygon_type == IffId("CURV")) { + nout << "Ignoring Catmull-Rom splines.\n"; + + } else if (_polygons->_polygon_type == IffId("PTCH")) { + nout << "Treating subdivision patches as ordinary polygons.\n"; + make_faces(); + + } else if (_polygons->_polygon_type == IffId("MBAL")) { + nout << "Ignoring metaballs.\n"; + + } else if (_polygons->_polygon_type == IffId("BONE")) { + nout << "Ignoring bones.\n"; + + } else if (_polygons->_polygon_type == IffId("FACE")) { + make_faces(); + + } else { + nout << "Ignoring unknown geometry type " << _polygons->_polygon_type + << ".\n"; + } +} + +/** + * Connects all the egg structures together. + */ +void CLwoPolygons:: +connect_egg() { + nassertv(_points->_layer->_egg_group != nullptr); + nassertv(_egg_group != nullptr); + _points->_layer->_egg_group->steal_children(*_egg_group); +} + + +/** + * Generates "face" polygons, i.e. actual polygons. + */ +void CLwoPolygons:: +make_faces() { + PN_stdfloat smooth_angle = -1.0; + + int num_polygons = _polygons->get_num_polygons(); + for (int pindex = 0; pindex < num_polygons; pindex++) { + LwoPolygons::Polygon *poly = _polygons->get_polygon(pindex); + CLwoSurface *surface = get_surface(pindex); + + bool is_valid = true; + + // Set up the vertices. + const LwoPoints *points = _points->_points; + int num_points = points->get_num_points(); + EggVertexPool *egg_vpool = _points->_egg_vpool; + + // We reverse the vertex ordering to compensate for Lightwave's clockwise + // ordering convention. We also want to start with the last vertex, so + // that the first convex angle is the first angle in the EggPolygon (for + // determining correct normals). + PT(EggPrimitive) egg_prim; + + if (poly->_vertices.size() == 1) { + egg_prim = new EggPoint; + } else { + egg_prim = new EggPolygon; + } + + // First, we have to create a temporary vector of vertices for the + // polygon, so we can possibly adjust the properties of these vertices + // (like the UV's) in the shader before we create them. + vector_PT_EggVertex egg_vertices; + + int num_vertices = poly->_vertices.size(); + for (int vi = num_vertices; vi > 0; vi--) { + int vindex = poly->_vertices[vi % num_vertices]; + if (vindex < 0 || vindex >= num_points) { + nout << "Invalid vertex index " << vindex << " in polygon.\n"; + is_valid = false; + } else { + PT(EggVertex) egg_vertex = new EggVertex; + LPoint3d pos = LCAST(double, points->get_point(vindex)); + egg_vertex->set_pos(pos); + + // Does the vertex used named UV's? + if (surface != nullptr && surface->has_named_uvs()) { + string uv_name = surface->get_uv_name(); + LPoint2 uv; + if (get_uv(uv_name, pindex, vindex, uv)) { + // This UV is defined in a "discontinuous" map, that associated a + // particular UV per each polygon. + egg_vertex->set_uv(LCAST(double, uv)); + + } else if (_points->get_uv(uv_name, vindex, uv)) { + // The UV does not appear in a discontinuous map, but it is + // defined in the points set. + egg_vertex->set_uv(LCAST(double, uv)); + } + } + + egg_vertices.push_back(egg_vertex); + } + } + + if (is_valid) { + if (surface != nullptr) { + surface->apply_properties(egg_prim, egg_vertices, smooth_angle); + } + + // Now add all the vertices officially to the primitive. + vector_PT_EggVertex::const_iterator evi; + for (evi = egg_vertices.begin(); evi != egg_vertices.end(); ++evi) { + EggVertex *egg_vertex = (*evi); + EggVertex *new_vertex = egg_vpool->create_unique_vertex(*egg_vertex); + egg_prim->add_vertex(new_vertex); + } + + // And add the primitive to its parent. + _egg_group->add_child(egg_prim.p()); + } + } + + CoordinateSystem cs = _converter->get_egg_data()->get_coordinate_system(); + if (smooth_angle > 0.0) { + _egg_group->recompute_vertex_normals(rad_2_deg(smooth_angle), cs); + } else { + _egg_group->recompute_polygon_normals(cs); + } +} diff --git a/pandatool/src/lwoegg/cLwoPolygons.h b/pandatool/src/lwoegg/cLwoPolygons.h new file mode 100644 index 00000000..d5c4b4af --- /dev/null +++ b/pandatool/src/lwoegg/cLwoPolygons.h @@ -0,0 +1,73 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoPolygons.h + * @author drose + * @date 2001-04-25 + */ + +#ifndef CLWOPOLYGONS_H +#define CLWOPOLYGONS_H + +#include "pandatoolbase.h" + +#include "lwoPolygons.h" +#include "eggGroup.h" +#include "pointerTo.h" + +#include "pmap.h" + +class LwoToEggConverter; +class CLwoPoints; +class CLwoSurface; +class LwoTags; +class LwoPolygonTags; +class LwoDiscontinuousVertexMap; + +/** + * This class is a wrapper around LwoPolygons and stores additional + * information useful during the conversion-to-egg process. + */ +class CLwoPolygons { +public: + INLINE CLwoPolygons(LwoToEggConverter *converter, + const LwoPolygons *polygons, + CLwoPoints *points); + + void add_ptags(const LwoPolygonTags *lwo_ptags, const LwoTags *tags); + void add_vmad(const LwoDiscontinuousVertexMap *lwo_vmad); + + CLwoSurface *get_surface(int polygon_index) const; + bool get_uv(const std::string &uv_name, int pi, int vi, LPoint2 &uv) const; + + void make_egg(); + void connect_egg(); + + LwoToEggConverter *_converter; + CPT(LwoPolygons) _polygons; + CLwoPoints *_points; + PT(EggGroup) _egg_group; + + const LwoTags *_tags; + typedef pmap PTags; + PTags _ptags; + + const LwoPolygonTags *_surf_ptags; + + // There might be named maps associated with the polygons to bring a per- + // polygon mapping to the UV's. + typedef pmap VMad; + VMad _txuv; + +private: + void make_faces(); +}; + +#include "cLwoPolygons.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoSurface.I b/pandatool/src/lwoegg/cLwoSurface.I new file mode 100644 index 00000000..8c39871a --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurface.I @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurface.I + * @author drose + * @date 2001-04-25 + */ + +/** + * Returns the name of the surface. Each surface in a given Lightwave file + * should have a unique name. + */ +INLINE const std::string &CLwoSurface:: +get_name() const { + return _surface->_name; +} + +/** + * Returns true if the surface is set up to reference UV's stored on the + * vertices, by name (as opposed to generated UV's, which is the more common + * Lightwave case). In this case, get_uv_name() can be called to return the + * name of the UV's. + */ +INLINE bool CLwoSurface:: +has_named_uvs() const { + return (_block != nullptr && + _block->_projection_mode == LwoSurfaceBlockProjection::M_uv); +} + +/** + * Returns the name of the set of UV's that are associated with this surface, + * if has_named_uvs() is true. + */ +INLINE const std::string &CLwoSurface:: +get_uv_name() const { + return _block->_uv_name; +} diff --git a/pandatool/src/lwoegg/cLwoSurface.cxx b/pandatool/src/lwoegg/cLwoSurface.cxx new file mode 100644 index 00000000..1bb0c146 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurface.cxx @@ -0,0 +1,495 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurface.cxx + * @author drose + * @date 2001-04-25 + */ + +#include "cLwoSurface.h" +#include "cLwoSurfaceBlock.h" +#include "cLwoClip.h" +#include "lwoToEggConverter.h" + +#include "lwoSurfaceColor.h" +#include "lwoSurfaceParameter.h" +#include "lwoSurfaceSmoothingAngle.h" +#include "lwoSurfaceSidedness.h" +#include "lwoSurfaceBlock.h" +#include "eggPrimitive.h" +#include "string_utils.h" +#include "mathNumbers.h" +#include "dcast.h" + + +/** + * + */ +CLwoSurface:: +CLwoSurface(LwoToEggConverter *converter, const LwoSurface *surface) : + _converter(converter), + _surface(surface) +{ + _flags = 0; + _rgb.set(1.0, 1.0, 1.0); + _checked_material = false; + _checked_texture = false; + _map_uvs = nullptr; + _block = nullptr; + + // Walk through the chunk list, looking for some basic properties. + int num_chunks = _surface->get_num_chunks(); + for (int i = 0; i < num_chunks; i++) { + const IffChunk *chunk = _surface->get_chunk(i); + + if (chunk->is_of_type(LwoSurfaceColor::get_class_type())) { + const LwoSurfaceColor *color = DCAST(LwoSurfaceColor, chunk); + _flags |= F_rgb; + _rgb = color->_color; + + } else if (chunk->is_of_type(LwoSurfaceParameter::get_class_type())) { + const LwoSurfaceParameter *param = DCAST(LwoSurfaceParameter, chunk); + IffId type = param->get_id(); + + if (type == IffId("DIFF")) { + _flags |= F_diffuse; + _diffuse = param->_value; + + } else if (type == IffId("LUMI")) { + _flags |= F_luminosity; + _luminosity = param->_value; + + } else if (type == IffId("SPEC")) { + _flags |= F_specular; + _specular = param->_value; + + } else if (type == IffId("REFL")) { + _flags |= F_reflection; + _reflection = param->_value; + + } else if (type == IffId("TRAN")) { + _flags |= F_transparency; + _transparency = param->_value; + + } else if (type == IffId("GLOS")) { + _flags |= F_gloss; + _gloss = param->_value; + + } else if (type == IffId("TRNL")) { + _flags |= F_translucency; + _translucency = param->_value; + } + + } else if (chunk->is_of_type(LwoSurfaceSmoothingAngle::get_class_type())) { + const LwoSurfaceSmoothingAngle *sa = DCAST(LwoSurfaceSmoothingAngle, chunk); + _flags |= F_smooth_angle; + _smooth_angle = sa->_angle; + + } else if (chunk->is_of_type(LwoSurfaceSidedness::get_class_type())) { + const LwoSurfaceSidedness *sn = DCAST(LwoSurfaceSidedness, chunk); + _flags |= F_backface; + _backface = (sn->_sidedness == LwoSurfaceSidedness::S_front_and_back); + + } else if (chunk->is_of_type(LwoSurfaceBlock::get_class_type())) { + const LwoSurfaceBlock *lwo_block = DCAST(LwoSurfaceBlock, chunk); + // One of possibly several blocks in the texture that define additional + // fancy rendering properties. + + CLwoSurfaceBlock *block = new CLwoSurfaceBlock(_converter, lwo_block); + + // We only consider enabled "IMAP" type blocks that affect "COLR". + if (block->_block_type == IffId("IMAP") && + block->_channel_id == IffId("COLR") && + block->_enabled) { + // Now save the block with the lowest ordinal. + if (_block == nullptr) { + _block = block; + + } else if (block->_ordinal < _block->_ordinal) { + delete _block; + _block = block; + + } else { + delete block; + } + + } else { + delete block; + } + } + } + + // Now get the four-component color, based on combining the RGB and the + // transparency. + _color.set(1.0, 1.0, 1.0, 1.0); + + if ((_flags & F_rgb) != 0) { + _color[0] = _rgb[0]; + _color[1] = _rgb[1]; + _color[2] = _rgb[2]; + } + + if ((_flags & F_transparency) != 0) { + _color[3] = 1.0 - _transparency; + } + + _diffuse_color = _color; +} + +/** + * + */ +CLwoSurface:: +~CLwoSurface() { + delete _block; +} + +/** + * Applies the color, texture, etc. described by the surface to the indicated + * egg primitive. + * + * If the surface defines a smoothing angle, smooth_angle may be updated to + * reflect it if the angle is greater than that specified. + */ +void CLwoSurface:: +apply_properties(EggPrimitive *egg_prim, vector_PT_EggVertex &egg_vertices, + PN_stdfloat &smooth_angle) { + if (!_surface->_source.empty()) { + // This surface is derived from another surface; apply that one first. + CLwoSurface *parent = _converter->get_surface(_surface->_source); + if (parent != nullptr && parent != this) { + parent->apply_properties(egg_prim, egg_vertices, smooth_angle); + } + } + + bool has_texture = check_texture(); + bool has_material = check_material(); + + egg_prim->set_color(_diffuse_color); + + if (has_material) { + egg_prim->set_material(_egg_material); + } + + if (has_texture) { + egg_prim->set_texture(_egg_texture); + + // Assign UV's to the vertices. + generate_uvs(egg_vertices); + } + + if ((_flags & F_backface) != 0) { + egg_prim->set_bface_flag(_backface); + } + + if ((_flags & F_smooth_angle) != 0) { + smooth_angle = std::max(smooth_angle, _smooth_angle); + } +} + +/** + * Checks whether the surface demands a texture or not. Returns true if so, + * false otherwise. + * + * If the surface demands a texture, this also sets up _egg_texture and + * _compute_uvs as appropriate for the texture. + */ +bool CLwoSurface:: +check_texture() { + if (_checked_texture) { + return (_egg_texture != nullptr); + } + _checked_texture = true; + _egg_texture = nullptr; + _map_uvs = nullptr; + + if (_block == nullptr) { + // No texture. Not even a shader block. + return false; + } + + int clip_index = _block->_clip_index; + if (clip_index < 0) { + // No image file associated with the texture. + return false; + } + + CLwoClip *clip = _converter->get_clip(clip_index); + if (clip == nullptr) { + nout << "No clip image with index " << clip_index << "\n"; + return false; + } + + if (!clip->is_still_image()) { + // Can't do anything with an animated image right now. + return false; + } + + Filename pathname = _converter->convert_model_path(clip->_filename); + + _egg_texture = new EggTexture("clip" + format_string(clip_index), pathname); + + // Do we need to generate UV's? + switch (_block->_projection_mode) { + case LwoSurfaceBlockProjection::M_planar: + _map_uvs = &CLwoSurface::map_planar; + break; + + case LwoSurfaceBlockProjection::M_cylindrical: + _map_uvs = &CLwoSurface::map_cylindrical; + break; + + case LwoSurfaceBlockProjection::M_spherical: + _map_uvs = &CLwoSurface::map_spherical; + break; + + case LwoSurfaceBlockProjection::M_cubic: + _map_uvs = &CLwoSurface::map_cubic; + break; + + case LwoSurfaceBlockProjection::M_front: + // Cannot generate "front" UV's, since this depends on a camera. Is it + // supposed to be updated in real time, like a projected texture? + break; + + case LwoSurfaceBlockProjection::M_uv: + // "uv" projection means to use the existing UV's already defined for the + // vertex. This case was already handled in the code that created the + // EggVertex pointers. + break; + }; + + // Texture overrides the primitive's natural color. + _color[0] = 1.0; + _color[1] = 1.0; + _color[2] = 1.0; + + return true; +} + +/** + * Checks whether the surface demands a material or not. Returns true if so, + * false otherwise. + */ +bool CLwoSurface:: +check_material() { + if (_checked_material) { + return (_egg_material != nullptr); + } + _checked_material = true; + _egg_material = nullptr; + + if (!_converter->_make_materials) { + // If we aren't making materials, then don't make a material. + return false; + } + + _egg_material = new EggMaterial(get_name()); + + if ((_flags & F_diffuse) != 0) { + _diffuse_color.set(_color[0] * _diffuse, + _color[1] * _diffuse, + _color[2] * _diffuse, + _color[3]); + // We want to avoid setting the diffuse color on the material. We're + // already setting the color explicitly on the object, so there's no need + // to also set a diffuse color on the material, and doing so prevents nice + // features like set_color() and set_color_scale() from working in Panda. + + // _egg_material->set_diff(_diffuse_color); + } + + if ((_flags & F_luminosity) != 0) { + LColor luminosity(_color[0] * _luminosity, + _color[1] * _luminosity, + _color[2] * _luminosity, + 1.0); + _egg_material->set_emit(luminosity); + } + + if ((_flags & F_specular) != 0) { + LColor specular(_color[0] * _specular, + _color[1] * _specular, + _color[2] * _specular, + 1.0); + _egg_material->set_spec(specular); + } + + if ((_flags & F_gloss) != 0) { + _egg_material->set_shininess(_gloss * 128.0); + } + + return true; +} + + +/** + * Computes all the UV's for the polygon's vertices, according to the + * _projection_mode defined in the block. + */ +void CLwoSurface:: +generate_uvs(vector_PT_EggVertex &egg_vertices) { + if (_map_uvs == nullptr) { + return; + } + + // To do this properly near seams and singularities (for instance, the back + // seam and the poles of the spherical map), we will need to know the + // polygon's centroid. + LPoint3d centroid(0.0, 0.0, 0.0); + + vector_PT_EggVertex::const_iterator vi; + for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) { + EggVertex *egg_vertex = (*vi); + centroid += egg_vertex->get_pos3(); + } + + centroid /= (double)egg_vertices.size(); + centroid = centroid * _block->_inv_transform; + + // Now go back through and actually compute the UV's. + for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) { + EggVertex *egg_vertex = (*vi); + LPoint3d pos = egg_vertex->get_pos3() * _block->_inv_transform; + LPoint2d uv = (this->*_map_uvs)(pos, centroid); + egg_vertex->set_uv(uv); + } +} + +/** + * Computes a UV based on the given point in space, using a planar projection. + */ +LPoint2d CLwoSurface:: +map_planar(const LPoint3d &pos, const LPoint3d &) const { + // A planar projection is about as easy as can be. We ignore the Y axis, + // and project the point into the XZ plane. Done. + double u = (pos[0] + 0.5); + double v = (pos[2] + 0.5); + + return LPoint2d(u, v); +} + +/** + * Computes a UV based on the given point in space, using a spherical + * projection. + */ +LPoint2d CLwoSurface:: +map_spherical(const LPoint3d &pos, const LPoint3d ¢roid) const { + // To compute the x position on the frame, we only need to consider the + // angle of the vector about the Y axis. Project the vector into the XZ + // plane to do this. + + LVector2d xz_orig(pos[0], pos[2]); + LVector2d xz = xz_orig; + double u_offset = 0.0; + + if (xz == LVector2d::zero()) { + // If we have a point on either pole, we've got problems. This point maps + // to the entire bottom edge of the image, so which U value should we + // choose? It does make a difference, especially if we have a number of + // polygons around the south pole that all share the common vertex. + + // We choose the U value based on the polygon's centroid. + xz.set(centroid[0], centroid[2]); + + } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) { + // Now, if our polygon crosses the seam along the back of the sphere--that + // is, the point is on the back of the sphere (xz[1] >= 0.0) and not on + // the same side of the XZ plane as the centroid, we've got problems too. + // We need to add an offset to the computed U value, either 1 or -1, to + // keep all the vertices of the polygon on the same side of the seam. + + u_offset = (xz[0] < 0.0) ? 1.0 : -1.0; + } + + // The U value is based on the longitude: the angle about the Y axis. + double u = + (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat; + + // Now rotate the vector into the YZ plane, and the V value is based on the + // latitude: the angle about the X axis. + LVector2d yz(pos[1], xz_orig.length()); + double v = + (atan2(yz[0], yz[1]) / MathNumbers::pi + 0.5) * _block->_h_repeat; + + return LPoint2d(u, v); +} + +/** + * Computes a UV based on the given point in space, using a cylindrical + * projection. + */ +LPoint2d CLwoSurface:: +map_cylindrical(const LPoint3d &pos, const LPoint3d ¢roid) const { + // This is almost identical to the spherical projection, except for the + // computation of V. + + LVector2d xz(pos[0], pos[2]); + double u_offset = 0.0; + + if (xz == LVector2d::zero()) { + // Although a cylindrical mapping does not really have a singularity at + // the pole, it's still possible to put a point there, and we'd like to do + // the right thing with the polygon that shares that point. So the + // singularity logic remains. + xz.set(centroid[0], centroid[2]); + + } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) { + // And cylinders do still have a seam at the back. + u_offset = (xz[0] < 0.0) ? 1.0 : -1.0; + } + + double u = + (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat; + + // For a cylindrical mapping, the V value comes almost directly from Y. + // Easy. + double v = (pos[1] + 0.5); + + return LPoint2d(u, v); +} + +/** + * Computes a UV based on the given point in space, using a cubic projection. + */ +LPoint2d CLwoSurface:: +map_cubic(const LPoint3d &pos, const LPoint3d ¢roid) const { + // A cubic projection is a planar projection, but we eliminate the dominant + // axis (based on the polygon's centroid) instead of arbitrarily eliminating + // Y. + + double x = fabs(centroid[0]); + double y = fabs(centroid[1]); + double z = fabs(centroid[2]); + + double u, v; + + if (x > y) { + if (x > z) { + // X is dominant. + u = (pos[2] + 0.5); + v = (pos[1] + 0.5); + } else { + // Z is dominant. + u = (pos[0] + 0.5); + v = (pos[1] + 0.5); + } + } else { + if (y > z) { + // Y is dominant. + u = (pos[0] + 0.5); + v = (pos[2] + 0.5); + } else { + // Z is dominant. + u = (pos[0] + 0.5); + v = (pos[1] + 0.5); + } + } + + return LPoint2d(u, v); +} diff --git a/pandatool/src/lwoegg/cLwoSurface.h b/pandatool/src/lwoegg/cLwoSurface.h new file mode 100644 index 00000000..ec098f18 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurface.h @@ -0,0 +1,109 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurface.h + * @author drose + * @date 2001-04-25 + */ + +#ifndef CLWOSURFACE_H +#define CLWOSURFACE_H + +#include "pandatoolbase.h" + +#include "cLwoSurfaceBlock.h" + +#include "lwoSurface.h" +#include "luse.h" +#include "eggTexture.h" +#include "eggMaterial.h" +#include "pt_EggTexture.h" +#include "pt_EggMaterial.h" +#include "vector_PT_EggVertex.h" + +#include "pmap.h" + +class LwoToEggConverter; +class LwoSurfaceBlock; +class EggPrimitive; + +/** + * This class is a wrapper around LwoSurface and stores additional information + * useful during the conversion-to-egg process. + */ +class CLwoSurface { +public: + CLwoSurface(LwoToEggConverter *converter, const LwoSurface *surface); + ~CLwoSurface(); + + INLINE const std::string &get_name() const; + + void apply_properties(EggPrimitive *egg_prim, + vector_PT_EggVertex &egg_vertices, + PN_stdfloat &smooth_angle); + bool check_texture(); + bool check_material(); + + INLINE bool has_named_uvs() const; + INLINE const std::string &get_uv_name() const; + + + enum Flags { + F_rgb = 0x0001, + F_diffuse = 0x0002, + F_luminosity = 0x0004, + F_specular = 0x0008, + F_reflection = 0x0010, + F_transparency = 0x0020, + F_gloss = 0x0040, + F_translucency = 0x0080, + F_smooth_angle = 0x0100, + F_backface = 0x0200, + }; + + int _flags; + LRGBColor _rgb; + PN_stdfloat _diffuse; + PN_stdfloat _luminosity; + PN_stdfloat _specular; + PN_stdfloat _reflection; + PN_stdfloat _transparency; + PN_stdfloat _gloss; + PN_stdfloat _translucency; + PN_stdfloat _smooth_angle; + bool _backface; + + LColor _color; + LColor _diffuse_color; + + LwoToEggConverter *_converter; + CPT(LwoSurface) _surface; + + bool _checked_material; + PT_EggMaterial _egg_material; + + bool _checked_texture; + PT_EggTexture _egg_texture; + + CLwoSurfaceBlock *_block; + +private: + void generate_uvs(vector_PT_EggVertex &egg_vertices); + + LPoint2d map_planar(const LPoint3d &pos, const LPoint3d ¢roid) const; + LPoint2d map_spherical(const LPoint3d &pos, const LPoint3d ¢roid) const; + LPoint2d map_cylindrical(const LPoint3d &pos, const LPoint3d ¢roid) const; + LPoint2d map_cubic(const LPoint3d &pos, const LPoint3d ¢roid) const; + + // Define a pointer to one of the above member functions. + LPoint2d (CLwoSurface::*_map_uvs)(const LPoint3d &pos, const LPoint3d ¢roid) const; +}; + +#include "cLwoSurface.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlock.I b/pandatool/src/lwoegg/cLwoSurfaceBlock.I new file mode 100644 index 00000000..dde99337 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlock.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlock.I + * @author drose + * @date 2001-04-26 + */ diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlock.cxx b/pandatool/src/lwoegg/cLwoSurfaceBlock.cxx new file mode 100644 index 00000000..ebf5a5ac --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlock.cxx @@ -0,0 +1,148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlock.cxx + * @author drose + * @date 2001-04-26 + */ + +#include "cLwoSurfaceBlock.h" +#include "cLwoSurfaceBlockTMap.h" +#include "lwoToEggConverter.h" + +#include "lwoSurfaceBlockChannel.h" +#include "lwoSurfaceBlockEnabled.h" +#include "lwoSurfaceBlockImage.h" +#include "lwoSurfaceBlockRepeat.h" +#include "lwoSurfaceBlockVMapName.h" +#include "dcast.h" + + +/** + * + */ +CLwoSurfaceBlock:: +CLwoSurfaceBlock(LwoToEggConverter *converter, const LwoSurfaceBlock *block) : + _converter(converter), + _block(block) +{ + _block_type = _block->_header->get_id(); + _ordinal = _block->_header->_ordinal; + _enabled = true; + _opacity_type = LwoSurfaceBlockOpacity::T_additive; + _opacity = 1.0; + _transform = LMatrix4d::ident_mat(); + _inv_transform = LMatrix4d::ident_mat(); + _projection_mode = LwoSurfaceBlockProjection::M_uv; + _axis = LwoSurfaceBlockAxis::A_y; + _clip_index = -1; + _w_wrap = LwoSurfaceBlockWrap::M_repeat; + _h_wrap = LwoSurfaceBlockWrap::M_repeat; + _w_repeat = 1.0; + _h_repeat = 1.0; + _tmap = nullptr; + + // Scan the chunks in the header. + int num_hchunks = _block->_header->get_num_chunks(); + for (int hi = 0; hi < num_hchunks; hi++) { + const IffChunk *hchunk = _block->_header->get_chunk(hi); + + if (hchunk->is_of_type(LwoSurfaceBlockChannel::get_class_type())) { + const LwoSurfaceBlockChannel *bc = + DCAST(LwoSurfaceBlockChannel, hchunk); + _channel_id = bc->_channel_id; + + } else if (hchunk->is_of_type(LwoSurfaceBlockEnabled::get_class_type())) { + const LwoSurfaceBlockEnabled *ec = + DCAST(LwoSurfaceBlockEnabled, hchunk); + _enabled = ec->_enabled; + } + } + + // Scan the chunks in the body. + int num_chunks = _block->get_num_chunks(); + for (int i = 0; i < num_chunks; i++) { + const IffChunk *chunk = _block->get_chunk(i); + + if (chunk->is_of_type(LwoSurfaceBlockTMap::get_class_type())) { + const LwoSurfaceBlockTMap *lwo_tmap = DCAST(LwoSurfaceBlockTMap, chunk); + if (_tmap != nullptr) { + nout << "Two TMAP chunks encountered within surface block.\n"; + delete _tmap; + } + _tmap = new CLwoSurfaceBlockTMap(_converter, lwo_tmap); + + } else if (chunk->is_of_type(LwoSurfaceBlockProjection::get_class_type())) { + const LwoSurfaceBlockProjection *proj = DCAST(LwoSurfaceBlockProjection, chunk); + _projection_mode = proj->_mode; + + } else if (chunk->is_of_type(LwoSurfaceBlockAxis::get_class_type())) { + const LwoSurfaceBlockAxis *axis = DCAST(LwoSurfaceBlockAxis, chunk); + _axis = axis->_axis; + + } else if (chunk->is_of_type(LwoSurfaceBlockImage::get_class_type())) { + const LwoSurfaceBlockImage *image = DCAST(LwoSurfaceBlockImage, chunk); + _clip_index = image->_index; + + } else if (chunk->is_of_type(LwoSurfaceBlockWrap::get_class_type())) { + const LwoSurfaceBlockWrap *wrap = DCAST(LwoSurfaceBlockWrap, chunk); + _w_wrap = wrap->_width; + _h_wrap = wrap->_height; + + } else if (chunk->is_of_type(LwoSurfaceBlockWrap::get_class_type())) { + const LwoSurfaceBlockWrap *wrap = DCAST(LwoSurfaceBlockWrap, chunk); + _w_wrap = wrap->_width; + _h_wrap = wrap->_height; + + } else if (chunk->is_of_type(LwoSurfaceBlockVMapName::get_class_type())) { + const LwoSurfaceBlockVMapName *vmap = DCAST(LwoSurfaceBlockVMapName, chunk); + _uv_name = vmap->_name; + + } else if (chunk->is_of_type(LwoSurfaceBlockRepeat::get_class_type())) { + const LwoSurfaceBlockRepeat *repeat = DCAST(LwoSurfaceBlockRepeat, chunk); + if (repeat->get_id() == IffId("WRPW")) { + _w_repeat = repeat->_cycles; + } else if (repeat->get_id() == IffId("WRPH")) { + _h_repeat = repeat->_cycles; + } + } + } + + if (_tmap != nullptr) { + _tmap->get_transform(_transform); + } + + // Also rotate the transform if we specify some axis other than Y. (All the + // map_* uv mapping functions are written to assume Y is the dominant axis.) + switch (_axis) { + case LwoSurfaceBlockAxis::A_x: + _transform = LMatrix4d::rotate_mat(90.0, + LVecBase3d::unit_z(), + CS_yup_left) * _transform; + break; + + case LwoSurfaceBlockAxis::A_y: + break; + + case LwoSurfaceBlockAxis::A_z: + _transform = LMatrix4d::rotate_mat(-90.0, + LVecBase3d::unit_x(), + CS_yup_left) * _transform; + break; + } + + _inv_transform.invert_from(_transform); +} + +/** + * + */ +CLwoSurfaceBlock:: +~CLwoSurfaceBlock() { + delete _tmap; +} diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlock.h b/pandatool/src/lwoegg/cLwoSurfaceBlock.h new file mode 100644 index 00000000..cf86126f --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlock.h @@ -0,0 +1,66 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlock.h + * @author drose + * @date 2001-04-26 + */ + +#ifndef CLWOSURFACEBLOCK_H +#define CLWOSURFACEBLOCK_H + +#include "pandatoolbase.h" + +#include "lwoSurfaceBlock.h" +#include "lwoSurfaceBlockOpacity.h" +#include "lwoSurfaceBlockProjection.h" +#include "lwoSurfaceBlockAxis.h" +#include "lwoSurfaceBlockWrap.h" + +#include "luse.h" + +class LwoToEggConverter; +class CLwoSurfaceBlockTMap; + +/** + * This class is a wrapper around LwoSurfaceBlock and stores additional + * information useful during the conversion-to-egg process. + */ +class CLwoSurfaceBlock { +public: + CLwoSurfaceBlock(LwoToEggConverter *converter, const LwoSurfaceBlock *block); + ~CLwoSurfaceBlock(); + + IffId _block_type; + IffId _channel_id; + std::string _ordinal; + bool _enabled; + + LwoSurfaceBlockOpacity::Type _opacity_type; + PN_stdfloat _opacity; + + LMatrix4d _transform; + LMatrix4d _inv_transform; + LwoSurfaceBlockProjection::Mode _projection_mode; + LwoSurfaceBlockAxis::Axis _axis; + + int _clip_index; + LwoSurfaceBlockWrap::Mode _w_wrap; + LwoSurfaceBlockWrap::Mode _h_wrap; + PN_stdfloat _w_repeat; + PN_stdfloat _h_repeat; + std::string _uv_name; + + LwoToEggConverter *_converter; + CPT(LwoSurfaceBlock) _block; + CLwoSurfaceBlockTMap *_tmap; +}; + +#include "cLwoSurfaceBlock.I" + +#endif diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.I b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.I new file mode 100644 index 00000000..f267006f --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlockTMap.I + * @author drose + * @date 2001-04-30 + */ diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.cxx b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.cxx new file mode 100644 index 00000000..649fd803 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.cxx @@ -0,0 +1,73 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlockTMap.cxx + * @author drose + * @date 2001-04-30 + */ + +#include "cLwoSurfaceBlockTMap.h" +#include "lwoToEggConverter.h" + +#include "lwoSurfaceBlockTransform.h" +#include "lwoSurfaceBlockRefObj.h" +#include "compose_matrix.h" +#include "dcast.h" + +/** + * + */ +CLwoSurfaceBlockTMap:: +CLwoSurfaceBlockTMap(LwoToEggConverter *converter, const LwoSurfaceBlockTMap *tmap) : + _converter(converter), + _tmap(tmap) +{ + _center.set(0.0, 0.0, 0.0); + _size.set(1.0, 1.0, 1.0); + _rotation.set(0.0, 0.0, 0.0); + _csys = LwoSurfaceBlockCoordSys::T_object; + _reference_object = "(none)"; + + // Scan the chunks in the body. + int num_chunks = _tmap->get_num_chunks(); + for (int i = 0; i < num_chunks; i++) { + const IffChunk *chunk = _tmap->get_chunk(i); + + if (chunk->is_of_type(LwoSurfaceBlockTransform::get_class_type())) { + const LwoSurfaceBlockTransform *trans = DCAST(LwoSurfaceBlockTransform, chunk); + if (trans->get_id() == IffId("CNTR")) { + _center = trans->_vec; + } else if (trans->get_id() == IffId("SIZE")) { + _size = trans->_vec; + } else if (trans->get_id() == IffId("ROTA")) { + _rotation = trans->_vec; + } + + } else if (chunk->is_of_type(LwoSurfaceBlockRefObj::get_class_type())) { + const LwoSurfaceBlockRefObj *ref = DCAST(LwoSurfaceBlockRefObj, chunk); + _reference_object = ref->_name; + + } else if (chunk->is_of_type(LwoSurfaceBlockCoordSys::get_class_type())) { + const LwoSurfaceBlockCoordSys *csys = DCAST(LwoSurfaceBlockCoordSys, chunk); + _csys = csys->_type; + } + } +} + +/** + * Fills up the indicated matrix with the net transform indicated by the TMAP + * chunk, accounting for scale, rotate, and translate. + */ +void CLwoSurfaceBlockTMap:: +get_transform(LMatrix4d &mat) const { + LPoint3d hpr(rad_2_deg(_rotation[0]), + rad_2_deg(-_rotation[1]), + rad_2_deg(-_rotation[2])); + compose_matrix(mat, LCAST(double, _size), hpr, + LCAST(double, _center), CS_yup_left); +} diff --git a/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.h b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.h new file mode 100644 index 00000000..075ed418 --- /dev/null +++ b/pandatool/src/lwoegg/cLwoSurfaceBlockTMap.h @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cLwoSurfaceBlockTMap.h + * @author drose + * @date 2001-04-30 + */ + +#ifndef CLWOSURFACEBLOCKTMAP_H +#define CLWOSURFACEBLOCKTMAP_H + +#include "pandatoolbase.h" + +#include "lwoSurfaceBlockTMap.h" +#include "lwoSurfaceBlockCoordSys.h" + +#include "luse.h" + +class LwoToEggConverter; + +/** + * This class is a wrapper around LwoSurfaceBlockTMap and stores additional + * information useful during the conversion-to-egg process. + */ +class CLwoSurfaceBlockTMap { +public: + CLwoSurfaceBlockTMap(LwoToEggConverter *converter, const LwoSurfaceBlockTMap *tmap); + + void get_transform(LMatrix4d &mat) const; + + LPoint3 _center; + LVecBase3 _size; + LVecBase3 _rotation; + + std::string _reference_object; + + LwoSurfaceBlockCoordSys::Type _csys; + + LwoToEggConverter *_converter; + CPT(LwoSurfaceBlockTMap) _tmap; +}; + +#include "cLwoSurfaceBlockTMap.I" + +#endif diff --git a/pandatool/src/lwoegg/lwoToEggConverter.I b/pandatool/src/lwoegg/lwoToEggConverter.I new file mode 100644 index 00000000..2e766681 --- /dev/null +++ b/pandatool/src/lwoegg/lwoToEggConverter.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoToEggConverter.I + * @author drose + * @date 2001-04-25 + */ diff --git a/pandatool/src/lwoegg/lwoToEggConverter.cxx b/pandatool/src/lwoegg/lwoToEggConverter.cxx new file mode 100644 index 00000000..524d60a8 --- /dev/null +++ b/pandatool/src/lwoegg/lwoToEggConverter.cxx @@ -0,0 +1,443 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoToEggConverter.cxx + * @author drose + * @date 2001-04-25 + */ + +#include "lwoToEggConverter.h" +#include "cLwoLayer.h" +#include "cLwoClip.h" +#include "cLwoPoints.h" +#include "cLwoPolygons.h" +#include "cLwoSurface.h" + +#include "eggData.h" +#include "lwoHeader.h" +#include "lwoLayer.h" +#include "lwoClip.h" +#include "lwoPoints.h" +#include "lwoPolygons.h" +#include "lwoVertexMap.h" +#include "lwoDiscontinuousVertexMap.h" +#include "lwoTags.h" +#include "lwoPolygonTags.h" +#include "lwoInputFile.h" +#include "dcast.h" + + +/** + * + */ +LwoToEggConverter:: +LwoToEggConverter() { + _generic_layer = nullptr; + _make_materials = true; +} + +/** + * + */ +LwoToEggConverter:: +LwoToEggConverter(const LwoToEggConverter ©) : + SomethingToEggConverter(copy) +{ +} + +/** + * + */ +LwoToEggConverter:: +~LwoToEggConverter() { + cleanup(); +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *LwoToEggConverter:: +make_copy() { + return new LwoToEggConverter(*this); +} + +/** + * Returns the English name of the file type this converter supports. + */ +std::string LwoToEggConverter:: +get_name() const { + return "Lightwave"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +std::string LwoToEggConverter:: +get_extension() const { + return "lwo"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool LwoToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + * + * This is designed to be as generic as possible, generally in support of run- + * time loading. Command-line converters may choose to use convert_lwo() + * instead, as it provides more control. + */ +bool LwoToEggConverter:: +convert_file(const Filename &filename) { + LwoInputFile in; + + nout << "Reading " << filename << "\n"; + if (!in.open_read(filename)) { + nout << "Unable to open " << filename << "\n"; + return false; + } + + PT(IffChunk) chunk = in.get_chunk(); + if (chunk == nullptr) { + nout << "Unable to read " << filename << "\n"; + return false; + } + + if (!chunk->is_of_type(LwoHeader::get_class_type())) { + nout << "File " << filename << " is not a Lightwave Object file.\n"; + return false; + } + + LwoHeader *header = DCAST(LwoHeader, chunk); + if (!header->is_valid()) { + nout << "File " << filename + << " is not recognized as a Lightwave Object file. " + << "Perhaps the version is too recent.\n"; + return false; + } + + return convert_lwo(header); +} + +/** + * Fills up the egg_data structure according to the indicated lwo structure. + */ +bool LwoToEggConverter:: +convert_lwo(const LwoHeader *lwo_header) { + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_yup_left); + } + + _error = false; + _lwo_header = lwo_header; + + collect_lwo(); + make_egg(); + connect_egg(); + + _egg_data->remove_unused_vertices(true); + cleanup(); + + return !had_error(); +} + +/** + * Returns a pointer to the layer with the given index number, or NULL if + * there is no such layer. + */ +CLwoLayer *LwoToEggConverter:: +get_layer(int number) const { + if (number >= 0 && number < (int)_layers.size()) { + return _layers[number]; + } + return nullptr; +} + +/** + * Returns a pointer to the clip with the given index number, or NULL if there + * is no such clip. + */ +CLwoClip *LwoToEggConverter:: +get_clip(int number) const { + if (number >= 0 && number < (int)_clips.size()) { + return _clips[number]; + } + return nullptr; +} + +/** + * Returns a pointer to the surface definition with the given name, or NULL if + * there is no such surface. + */ +CLwoSurface *LwoToEggConverter:: +get_surface(const std::string &name) const { + Surfaces::const_iterator si; + si = _surfaces.find(name); + if (si != _surfaces.end()) { + return (*si).second; + } + return nullptr; +} + +/** + * Frees all the internal data structures after we're done converting, and + * resets the converter to its initial state. + */ +void LwoToEggConverter:: +cleanup() { + _lwo_header.clear(); + + delete _generic_layer; + _generic_layer = nullptr; + + for (CLwoLayer *layer : _layers) { + delete layer; + } + _layers.clear(); + + for (CLwoClip *clip : _clips) { + delete clip; + } + _clips.clear(); + + for (CLwoPoints *points : _points) { + delete points; + } + _points.clear(); + + for (CLwoPolygons *polygons : _polygons) { + delete polygons; + } + _polygons.clear(); + + Surfaces::iterator si; + for (si = _surfaces.begin(); si != _surfaces.end(); ++si) { + CLwoSurface *surface = (*si).second; + delete surface; + } + _surfaces.clear(); +} + +/** + * Walks through the chunks in the Lightwave data and creates wrapper objects + * for each relevant piece. + */ +void LwoToEggConverter:: +collect_lwo() { + CLwoLayer *last_layer = nullptr; + CLwoPoints *last_points = nullptr; + CLwoPolygons *last_polygons = nullptr; + + const LwoTags *tags = nullptr; + + int num_chunks = _lwo_header->get_num_chunks(); + for (int i = 0; i < num_chunks; i++) { + const IffChunk *chunk = _lwo_header->get_chunk(i); + + if (chunk->is_of_type(LwoLayer::get_class_type())) { + const LwoLayer *lwo_layer = DCAST(LwoLayer, chunk); + CLwoLayer *layer = new CLwoLayer(this, lwo_layer); + int number = layer->get_number(); + slot_layer(number); + + if (_layers[number] != nullptr) { + nout << "Warning: multiple layers with number " << number << "\n"; + } + _layers[number] = layer; + last_layer = layer; + last_points = nullptr; + last_polygons = nullptr; + + } else if (chunk->is_of_type(LwoClip::get_class_type())) { + const LwoClip *lwo_clip = DCAST(LwoClip, chunk); + CLwoClip *clip = new CLwoClip(this, lwo_clip); + + int index = clip->get_index(); + slot_clip(index); + + if (_clips[index] != nullptr) { + nout << "Warning: multiple clips with index " << index << "\n"; + } + _clips[index] = clip; + + } else if (chunk->is_of_type(LwoPoints::get_class_type())) { + if (last_layer == nullptr) { + last_layer = make_generic_layer(); + } + + const LwoPoints *lwo_points = DCAST(LwoPoints, chunk); + CLwoPoints *points = new CLwoPoints(this, lwo_points, last_layer); + _points.push_back(points); + last_points = points; + + } else if (chunk->is_of_type(LwoVertexMap::get_class_type())) { + if (last_points == nullptr) { + nout << "Vertex map chunk encountered without a preceding points chunk.\n"; + } else { + const LwoVertexMap *lwo_vmap = DCAST(LwoVertexMap, chunk); + last_points->add_vmap(lwo_vmap); + } + + } else if (chunk->is_of_type(LwoDiscontinuousVertexMap::get_class_type())) { + if (last_polygons == nullptr) { + nout << "Discontinous vertex map chunk encountered without a preceding polygons chunk.\n"; + } else { + const LwoDiscontinuousVertexMap *lwo_vmad = DCAST(LwoDiscontinuousVertexMap, chunk); + last_polygons->add_vmad(lwo_vmad); + } + + } else if (chunk->is_of_type(LwoTags::get_class_type())) { + tags = DCAST(LwoTags, chunk); + + } else if (chunk->is_of_type(LwoPolygons::get_class_type())) { + if (last_points == nullptr) { + nout << "Polygon chunk encountered without a preceding points chunk.\n"; + } else { + const LwoPolygons *lwo_polygons = DCAST(LwoPolygons, chunk); + CLwoPolygons *polygons = + new CLwoPolygons(this, lwo_polygons, last_points); + _polygons.push_back(polygons); + last_polygons = polygons; + } + + } else if (chunk->is_of_type(LwoPolygonTags::get_class_type())) { + if (last_polygons == nullptr) { + nout << "Polygon tags chunk encountered without a preceding polygons chunk.\n"; + } else if (tags == nullptr) { + nout << "Polygon tags chunk encountered without a preceding tags chunk.\n"; + } else { + const LwoPolygonTags *lwo_ptags = DCAST(LwoPolygonTags, chunk); + last_polygons->add_ptags(lwo_ptags, tags); + } + + } else if (chunk->is_of_type(LwoSurface::get_class_type())) { + if (last_layer == nullptr) { + last_layer = make_generic_layer(); + } + + const LwoSurface *lwo_surface = DCAST(LwoSurface, chunk); + CLwoSurface *surface = new CLwoSurface(this, lwo_surface); + + bool inserted = _surfaces.insert(Surfaces::value_type(surface->get_name(), surface)).second; + if (!inserted) { + nout << "Multiple surface definitions named " << surface->get_name() << "\n"; + delete surface; + } + } + } +} + +/** + * Makes egg structures for all of the conversion wrapper objects. + */ +void LwoToEggConverter:: +make_egg() { + if (_generic_layer != nullptr) { + _generic_layer->make_egg(); + } + + Layers::iterator li; + for (li = _layers.begin(); li != _layers.end(); ++li) { + CLwoLayer *layer = (*li); + if (layer != nullptr) { + layer->make_egg(); + } + } + + Points::iterator pi; + for (pi = _points.begin(); pi != _points.end(); ++pi) { + CLwoPoints *points = (*pi); + points->make_egg(); + } + + Polygons::iterator gi; + for (gi = _polygons.begin(); gi != _polygons.end(); ++gi) { + CLwoPolygons *polygons = (*gi); + polygons->make_egg(); + } +} + +/** + * Connects together all of the egg structures. + */ +void LwoToEggConverter:: +connect_egg() { + if (_generic_layer != nullptr) { + _generic_layer->connect_egg(); + } + + Layers::iterator li; + for (li = _layers.begin(); li != _layers.end(); ++li) { + CLwoLayer *layer = (*li); + if (layer != nullptr) { + layer->connect_egg(); + } + } + + Points::iterator pi; + for (pi = _points.begin(); pi != _points.end(); ++pi) { + CLwoPoints *points = (*pi); + points->connect_egg(); + } + + Polygons::iterator gi; + for (gi = _polygons.begin(); gi != _polygons.end(); ++gi) { + CLwoPolygons *polygons = (*gi); + polygons->connect_egg(); + } +} + +/** + * Ensures that there is space in the _layers array to store an element at + * position number. + */ +void LwoToEggConverter:: +slot_layer(int number) { + nassertv(number - (int)_layers.size() < 1000); + while (number >= (int)_layers.size()) { + _layers.push_back(nullptr); + } + nassertv(number >= 0 && number < (int)_layers.size()); +} + +/** + * Ensures that there is space in the _clips array to store an element at + * position number. + */ +void LwoToEggConverter:: +slot_clip(int number) { + nassertv(number - (int)_clips.size() < 1000); + while (number >= (int)_clips.size()) { + _clips.push_back(nullptr); + } + nassertv(number >= 0 && number < (int)_clips.size()); +} + +/** + * If a geometry definition is encountered in the Lightwave file before a + * layer definition, we should make a generic layer to hold the geometry. + * This makes and returns a single layer for this purpose. It should not be + * called twice. + */ +CLwoLayer *LwoToEggConverter:: +make_generic_layer() { + nassertr(_generic_layer == nullptr, _generic_layer); + + PT(LwoLayer) layer = new LwoLayer; + layer->make_generic(); + + _generic_layer = new CLwoLayer(this, layer); + return _generic_layer; +} diff --git a/pandatool/src/lwoegg/lwoToEggConverter.h b/pandatool/src/lwoegg/lwoToEggConverter.h new file mode 100644 index 00000000..c4380a21 --- /dev/null +++ b/pandatool/src/lwoegg/lwoToEggConverter.h @@ -0,0 +1,92 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoToEggConverter.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef LWOTOEGGCONVERTER_H +#define LWOTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "somethingToEggConverter.h" +#include "lwoHeader.h" +#include "pointerTo.h" + +#include "pvector.h" +#include "pmap.h" + +class CLwoLayer; +class CLwoClip; +class CLwoPoints; +class CLwoPolygons; +class CLwoSurface; +class LwoClip; + +/** + * This class supervises the construction of an EggData structure from the + * data represented by the LwoHeader. Reading and writing the egg and lwo + * structures is left to the user. + */ +class LwoToEggConverter : public SomethingToEggConverter { +public: + LwoToEggConverter(); + LwoToEggConverter(const LwoToEggConverter ©); + virtual ~LwoToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + + virtual bool convert_file(const Filename &filename); + bool convert_lwo(const LwoHeader *lwo_header); + virtual bool supports_compressed() const; + + CLwoLayer *get_layer(int number) const; + CLwoClip *get_clip(int number) const; + + CLwoSurface *get_surface(const std::string &name) const; + + bool _make_materials; + +private: + void cleanup(); + + void collect_lwo(); + void make_egg(); + void connect_egg(); + + void slot_layer(int number); + void slot_clip(int number); + CLwoLayer *make_generic_layer(); + + CPT(LwoHeader) _lwo_header; + + CLwoLayer *_generic_layer; + typedef pvector Layers; + Layers _layers; + + typedef pvector Clips; + Clips _clips; + + typedef pvector Points; + Points _points; + + typedef pvector Polygons; + Polygons _polygons; + + typedef pmap Surfaces; + Surfaces _surfaces; +}; + +#include "lwoToEggConverter.I" + +#endif diff --git a/pandatool/src/lwoegg/p3lwoegg_composite1.cxx b/pandatool/src/lwoegg/p3lwoegg_composite1.cxx new file mode 100644 index 00000000..b5106cfd --- /dev/null +++ b/pandatool/src/lwoegg/p3lwoegg_composite1.cxx @@ -0,0 +1,11 @@ + +#include "cLwoClip.cxx" +#include "cLwoLayer.cxx" +#include "cLwoPoints.cxx" +#include "cLwoPolygons.cxx" +#include "cLwoSurface.cxx" +#include "cLwoSurfaceBlock.cxx" +#include "cLwoSurfaceBlockTMap.cxx" +#include "lwoToEggConverter.cxx" +#include "lwoToEggConverter.h" + diff --git a/pandatool/src/lwoprogs/CMakeLists.txt b/pandatool/src/lwoprogs/CMakeLists.txt new file mode 100644 index 00000000..39210c50 --- /dev/null +++ b/pandatool/src/lwoprogs/CMakeLists.txt @@ -0,0 +1,15 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(lwo-scan lwoScan.cxx lwoScan.h) +target_link_libraries(lwo-scan p3progbase p3lwo) +install(TARGETS lwo-scan EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(lwo2egg lwoToEgg.cxx lwoToEgg.h) + target_link_libraries(lwo2egg p3lwoegg p3eggbase p3progbase) + install(TARGETS lwo2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/lwoprogs/lwoScan.cxx b/pandatool/src/lwoprogs/lwoScan.cxx new file mode 100644 index 00000000..c299a3a7 --- /dev/null +++ b/pandatool/src/lwoprogs/lwoScan.cxx @@ -0,0 +1,84 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoScan.cxx + * @author drose + * @date 2001-04-24 + */ + +#include "lwoScan.h" + +#include "lwoInputFile.h" +#include "lwoChunk.h" +#include "config_lwo.h" + +/** + * + */ +LwoScan:: +LwoScan() { + clear_runlines(); + add_runline("[opts] input.lwo"); + + set_program_brief("describe the contents of a Lightwave object file"); + set_program_description + ("This program simply reads a Lightwave object file and dumps its " + "contents to standard output. It's mainly useful for debugging " + "problems with lwo2egg."); +} + +/** + * + */ +void LwoScan:: +run() { + LwoInputFile in; + if (!in.open_read(_input_filename)) { + nout << "Unable to open " << _input_filename << "\n"; + exit(1); + } + + PT(IffChunk) chunk = in.get_chunk(); + if (chunk == nullptr) { + nout << "Unable to read file.\n"; + } else { + while (chunk != nullptr) { + chunk->write(std::cout, 0); + chunk = in.get_chunk(); + } + } +} + +/** + * + */ +bool LwoScan:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the Lightwave object file to read on the command line.\n"; + return false; + } + if (args.size() != 1) { + nout << "You may specify only one Lightwave object file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int +main(int argc, char *argv[]) { + init_liblwo(); + LwoScan prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/lwoprogs/lwoScan.h b/pandatool/src/lwoprogs/lwoScan.h new file mode 100644 index 00000000..a602e785 --- /dev/null +++ b/pandatool/src/lwoprogs/lwoScan.h @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoScan.h + * @author drose + * @date 2001-04-30 + */ + +#ifndef LWOSCAN_H +#define LWOSCAN_H + +#include "programBase.h" +#include "filename.h" + +/** + * A program to read a Lightwave file and report its structure and contents. + */ +class LwoScan : public ProgramBase { +public: + LwoScan(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; +}; + +#endif diff --git a/pandatool/src/lwoprogs/lwoToEgg.cxx b/pandatool/src/lwoprogs/lwoToEgg.cxx new file mode 100644 index 00000000..a2e54b48 --- /dev/null +++ b/pandatool/src/lwoprogs/lwoToEgg.cxx @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoToEgg.cxx + * @author drose + * @date 2001-04-17 + */ + +#include "lwoToEgg.h" + +#include "lwoToEggConverter.h" +#include "lwoHeader.h" +#include "lwoInputFile.h" +#include "config_lwo.h" + +/** + * + */ +LwoToEgg:: +LwoToEgg() : + SomethingToEgg("Lightwave", ".lwo") +{ + add_path_replace_options(); + add_path_store_options(); + add_units_options(); + add_normals_options(); + add_transform_options(); + + set_program_brief("convert a Lightwave Object file to .egg"); + set_program_description + ("This program converts Lightwave Object (.lwo) files to egg. Many " + "rendering characteristics of Lightwave (like layered shaders, etc.) " + "are not supported, but fundamental things like polygons and texture " + "maps are. This program is primarily designed to support files written " + "by Lightwave version 6.x (LWO2 files), but it also has some limited " + "support for version 5.x files (LWOB files)."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is y-up-left."); + + redescribe_option + ("ui", + "Specify the units of the input Lightwave file. By convention, " + "this is assumed to be meters if it is unspecified."); + + _coordinate_system = CS_yup_left; +} + +/** + * + */ +void LwoToEgg:: +run() { + _data->set_coordinate_system(_coordinate_system); + + if (_input_units == DU_invalid) { + _input_units = DU_meters; + } + + LwoToEggConverter converter; + converter.set_egg_data(_data); + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + init_liblwo(); + LwoToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/lwoprogs/lwoToEgg.h b/pandatool/src/lwoprogs/lwoToEgg.h new file mode 100644 index 00000000..44342fce --- /dev/null +++ b/pandatool/src/lwoprogs/lwoToEgg.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file lwoToEgg.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef LWOTOEGG_H +#define LWOTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "lwoToEggConverter.h" + +#include "dSearchPath.h" + +/** + * A program to read a Lightwave file and generate an egg file. + */ +class LwoToEgg : public SomethingToEgg { +public: + LwoToEgg(); + + void run(); +}; + +#endif diff --git a/pandatool/src/mac-stats/CMakeLists.txt b/pandatool/src/mac-stats/CMakeLists.txt new file mode 100644 index 00000000..23c54ef9 --- /dev/null +++ b/pandatool/src/mac-stats/CMakeLists.txt @@ -0,0 +1,57 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT APPLE OR NOT HAVE_NET) + return() +endif() + +set(MACSTATS_HEADERS + macStats.h + macStatsAppDelegate.h + macStatsChartMenu.h + macStatsChartMenuDelegate.h + macStatsFlameGraph.h + macStatsGraph.h + macStatsGraphView.h + macStatsGraphViewController.h + macStatsLabel.h + macStatsLabelStack.h + macStatsMonitor.h + macStatsPianoRoll.h + macStatsScaleArea.h + macStatsServer.h + macStatsStripChart.h + macStatsTimeline.h +) + +set(MACSTATS_SOURCES + macStats.mm + macStatsAppDelegate.mm + macStatsChartMenu.mm + macStatsChartMenuDelegate.mm + macStatsFlameGraph.mm + macStatsGraph.mm + macStatsGraphView.mm + macStatsGraphViewController.mm + macStatsLabel.mm + macStatsLabelStack.mm + macStatsMonitor.mm + macStatsPianoRoll.mm + macStatsScaleArea.mm + macStatsServer.mm + macStatsStripChart.mm + macStatsTimeline.mm +) + +composite_sources(mac-stats MACSTATS_SOURCES) +add_executable(mac-stats ${MACSTATS_HEADERS} ${MACSTATS_SOURCES}) +target_link_libraries(mac-stats p3progbase p3pstatserver) +target_link_libraries(mac-stats "-framework Cocoa") +target_link_libraries(mac-stats "-framework Carbon") +target_link_libraries(mac-stats "-framework Quartz") + +# This program is NOT actually called win-stats. It's just pstats +set_target_properties(mac-stats PROPERTIES OUTPUT_NAME "pstats") + +install(TARGETS mac-stats EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/mac-stats/Info.plist b/pandatool/src/mac-stats/Info.plist new file mode 100644 index 00000000..2550b7e5 --- /dev/null +++ b/pandatool/src/mac-stats/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + org.panda3d.pstats + CFBundleExecutable + pstats + CFBundleName + PStats + CFBundleDisplayName + PStats + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + LSHasLocalizedDisplayName + + NSAppleScriptEnabled + + NSPrincipalClass + NSApplication + CFBundleDocumentTypes + + + CFBundleTypeName + PStats session file + CFBundleTypeRole + Editor + LSHandlerRank + Owner + LSTypeIsPackage + + CFBundleTypeExtensions + + pstats + + CFBundleTypeMIMETypes + + application/vnd.panda3d.pstats + + + + + diff --git a/pandatool/src/mac-stats/cocoa_compat.h b/pandatool/src/mac-stats/cocoa_compat.h new file mode 100644 index 00000000..b535b08d --- /dev/null +++ b/pandatool/src/mac-stats/cocoa_compat.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file cocoa_compat.h + * @author rdb + * @date 2023-08-28 + */ + +#ifndef COCOA_COMPAT_H +#define COCOA_COMPAT_H + +#import + +// Allow building with older SDKs. +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 +typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) { + NSWindowToolbarStyleAutomatic, + NSWindowToolbarStyleExpanded, + NSWindowToolbarStylePreference, + NSWindowToolbarStyleUnified, + NSWindowToolbarStyleUnifiedCompact +} API_AVAILABLE(macos(11.0)); + +API_AVAILABLE(macos(11.0)) API_UNAVAILABLE(ios) +@interface NSTrackingSeparatorToolbarItem : NSToolbarItem ++ (instancetype)trackingSeparatorToolbarItemWithIdentifier:(NSString *)identifier splitView:(NSSplitView *)splitView dividerIndex:(NSInteger)dividerIndex API_UNAVAILABLE(ios); +@property (strong) NSSplitView *splitView API_UNAVAILABLE(ios); +@property NSInteger dividerIndex API_UNAVAILABLE(ios); +@end + +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED + +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400 +@protocol NSViewToolTipOwner +- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(nullable void *)data; +@end + +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED + +#endif // COCOA_COMPAT_H diff --git a/pandatool/src/mac-stats/macStats.h b/pandatool/src/mac-stats/macStats.h new file mode 100644 index 00000000..a5e2371b --- /dev/null +++ b/pandatool/src/mac-stats/macStats.h @@ -0,0 +1,19 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStats.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATS_H +#define MACSTATS_H + +#include "pStatServer.h" + +#endif diff --git a/pandatool/src/mac-stats/macStats.mm b/pandatool/src/mac-stats/macStats.mm new file mode 100644 index 00000000..cbaee082 --- /dev/null +++ b/pandatool/src/mac-stats/macStats.mm @@ -0,0 +1,73 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStats.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "pandatoolbase.h" +#include "macStats.h" +#include "macStatsServer.h" +#include "config_pstatclient.h" + +#include +#include + +extern "C" { + OSStatus CPSSetProcessName(ProcessSerialNumber *psn, char *name); +}; + +@implementation NSBundle(swizzle) +- (NSString *)__bundleIdentifier { + if (self == [NSBundle mainBundle]) { + return @"org.panda3d.pstats"; + } else { + return [self __bundleIdentifier]; + } +} +@end + +void +keyboard_interrupt(int sig) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + [NSApp terminate:NSApp]; + }); +} + +int +main(int argc, char *argv[]) { + // This hack is necessary to allow showing notifications when run as a console app. + Class cls = objc_getClass("NSBundle"); + if (cls) { + method_exchangeImplementations(class_getInstanceMethod(cls, @selector(bundleIdentifier)), + class_getInstanceMethod(cls, @selector(__bundleIdentifier))); + } + + // Set the bundle name of the application. + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + CPSSetProcessName(&psn, (char *)"PStats"); + + @autoreleasepool { + // Create the server application. + MacStatsServer *server = new MacStatsServer; + + // Register a SIGINT handler to terminate the application correctly. + // Otherwise, notifications may linger in the notification center. + struct sigaction act = {}; + act.sa_handler = &keyboard_interrupt; + act.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &act, nullptr); + + // Get lost in the Cocoa main loop. + server->run(argc, argv); + } + + return 0; +} diff --git a/pandatool/src/mac-stats/macStatsAppDelegate.h b/pandatool/src/mac-stats/macStatsAppDelegate.h new file mode 100644 index 00000000..e7921178 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsAppDelegate.h @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsAppDelegate.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATSAPPDELEGATE_H +#define MACSTATSAPPDELEGATE_H + +#import +#import + +class MacStatsServer; + +@interface MacStatsAppDelegate : NSObject { + @private + MacStatsServer *_server; + NSTimer *_timer; +} + +- (id)initWithServer:(MacStatsServer *)server; +- (void)applicationDidFinishLaunching:(NSApplication *)sender; +- (BOOL)applicationShouldTerminate:(NSApplication *)sender; +- (void)applicationWillTerminate:(NSApplication *)sender; +- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center + shouldPresentNotification:(NSUserNotification *)notification; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsAppDelegate.mm b/pandatool/src/mac-stats/macStatsAppDelegate.mm new file mode 100644 index 00000000..7831549c --- /dev/null +++ b/pandatool/src/mac-stats/macStatsAppDelegate.mm @@ -0,0 +1,201 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsAppDelegate.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "macStatsAppDelegate.h" +#include "macStatsServer.h" +#include "pStatGraph.h" + +@implementation MacStatsAppDelegate + +- (id)initWithServer:(MacStatsServer *)server { + if (self = [super init]) { + _server = server; + _timer = nil; + } + + return self; +} + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { + // Squelches an annoying warning. + return YES; +} + +- (BOOL)application:(NSApplication *)sender + openFile:(NSString *)filename { + Filename fn([filename UTF8String]); + fn.set_binary(); + return _server->open_session(fn); +} + +- (void)applicationDidFinishLaunching:(NSApplication *)sender { + // Set this object as delegate for user notifications. + { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + center.delegate = self; + } + + // Register default preferences. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *dict = [NSDictionary + dictionaryWithObjects:@[@"", [NSNumber numberWithBool:YES], [NSNumber numberWithInt:PStatGraph::GBU_ms]] + forKeys:@[@"Appearance", @"ShowStatusItem", @"TimeUnits"]]; + [defaults registerDefaults:dict]; + + if (_server != nil) { + // Apply preferences. + [self applyDefaults:nil]; + + // Create a timer to poll the server. + _timer = [NSTimer scheduledTimerWithTimeInterval:0.2 + target:self + selector:@selector(pollServer) + userInfo:nil + repeats:YES]; + + // Start new session if we don't have one yet. + if (_server->get_monitor() == nullptr) { + _server->new_session(); + } + } + + // Watch for defaults change. + { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(applyDefaults:) + name:NSUserDefaultsDidChangeNotification + object:nil]; + } +} + +- (void)applyDefaults:(NSNotification *)notification { + if (_server != nil) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + _server->set_show_status_item([defaults boolForKey:@"ShowStatusItem"]); + _server->set_appearance([defaults stringForKey:@"Appearance"]); + _server->set_time_units([defaults integerForKey:@"TimeUnits"]); + } +} + +- (void)pollServer { + _server->poll(); +} + +- (BOOL)applicationShouldTerminate:(NSApplication *)sender { + if (_server != nil) { + return _server->close_session(); + } + return YES; +} + +- (void)applicationWillTerminate:(NSApplication *)sender { + if (_timer != nil) { + [_timer invalidate]; + _timer = nil; + } + + if (_server != nil) { + delete _server; + _server = nil; + } +} + +- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center + shouldPresentNotification:(NSUserNotification *)notification { + return YES; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center + didActivateNotification:(NSUserNotification *)notification { + NSUserNotificationAction *action = notification.additionalActivationAction; + if (action != nil) { + NSString *ident = action.identifier; + if ([ident isEqual:@"quit"]) { + [NSApp terminate:self]; + } + else if ([ident isEqual:@"new"]) { + _server->new_session(); + } + else if ([ident isEqual:@"open"]) { + _server->open_session(); + } + else if ([ident isEqual:@"openLast"]) { + _server->open_last_session(); + } + } +} + +- (void)handleNewSession:(NSMenuItem *)item { + _server->new_session(); +} + +- (void)handleOpenSession:(NSMenuItem *)item { + _server->open_session(); +} + +- (void)handleOpenLastSession:(NSMenuItem *)item { + _server->open_last_session(); +} + +- (void)handleSaveSession:(NSMenuItem *)item { + _server->save_session(); +} + +- (void)handleCloseSession:(NSMenuItem *)item { + _server->close_session(); +} + +- (void)handleExportSession:(NSMenuItem *)item { + _server->export_session(); +} + +- (void)handleToggleSettingsBool:(NSMenuItem *)item { + [[NSUserDefaults standardUserDefaults] setBool:(item.state != NSOnState) forKey:item.representedObject]; +} + +- (void)handleSettingsInteger:(NSMenuItem *)item { + [[NSUserDefaults standardUserDefaults] setInteger:item.tag forKey:item.representedObject]; +} + +- (void)handleSettingsAppearance:(NSMenuItem *)item { + [[NSUserDefaults standardUserDefaults] setObject:item.representedObject forKey:@"Appearance"]; +} + +- (void)handleSpeed:(NSMenuItem *)item { + MacStatsMonitor *monitor = _server->get_monitor(); + if (monitor != nullptr) { + monitor->set_scroll_speed(item.tag); + } +} + +- (void)handlePause:(NSMenuItem *)item { + MacStatsMonitor *monitor = _server->get_monitor(); + if (monitor != nullptr) { + monitor->set_pause(!item.state); + } +} + +- (void)handleClickStatusItem:(id)sender { + [NSApp activateIgnoringOtherApps:YES]; +} + +- (void)handleChooseCollectorColor:(NSColorPanel *)panel { + MacStatsMonitor *monitor = _server->get_monitor(); + if (monitor != nullptr) { + NSColor *color = panel.color; + monitor->handle_choose_collector_color(LRGBColor(color.redComponent, color.greenComponent, color.blueComponent)); + } +} + +@end diff --git a/pandatool/src/mac-stats/macStatsChartMenu.h b/pandatool/src/mac-stats/macStatsChartMenu.h new file mode 100644 index 00000000..a8b3bcea --- /dev/null +++ b/pandatool/src/mac-stats/macStatsChartMenu.h @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsChartMenu.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSCHARTMENU_H +#define MACSTATSCHARTMENU_H + +#include "pandatoolbase.h" +#include "macStatsMonitor.h" + +#include + +class PStatView; +class PStatViewLevel; + +/** + * A pulldown menu of charts available for a particular thread. + */ +class MacStatsChartMenu { +public: + MacStatsChartMenu(MacStatsMonitor *monitor, int thread_index); + ~MacStatsChartMenu(); + + int get_thread_index() const { return _thread_index; } + + void add_to_menu(NSMenu *menu, int position); + void remove_from_menu(NSMenu *menu); + + void check_update(); + void do_update(); + +private: + bool add_view(NSMenu *parent_menu, const PStatViewLevel *view_level, + bool show_level, int insert_at); + NSMenuItem *make_menu_item(NSMenu *parent_menu, int insert_at, + const char *label, SEL action, + int collector_index = -1); + + MacStatsMonitor *_monitor; + int _thread_index; + + int _last_level_index; + NSMenu *_menu; + + // Pair of menu item, submenu + std::vector > _collector_items; + int _time_items_end = 0; + int _level_items_end = 0; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsChartMenu.mm b/pandatool/src/mac-stats/macStatsChartMenu.mm new file mode 100644 index 00000000..8df12743 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsChartMenu.mm @@ -0,0 +1,246 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsChartMenu.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsChartMenu.h" +#include "macStatsChartMenuDelegate.h" +#include "macStatsMonitor.h" + +/** + * + */ +MacStatsChartMenu:: +MacStatsChartMenu(MacStatsMonitor *monitor, int thread_index) : + _monitor(monitor), + _thread_index(thread_index) +{ + _menu = [[NSMenu alloc] init]; + _menu.delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index]; + + if (thread_index == 0) { + _menu.title = @"Graphs"; + + // Timeline goes first. + make_menu_item(_menu, -1, "Timeline", @selector(handleOpenTimeline:)); + + // Then the piano roll (even though it's not very useful nowadays) + make_menu_item(_menu, -1, "Piano Roll", @selector(handleOpenPianoRoll:)); + } + else { + make_menu_item(_menu, -1, "Open Strip Chart", @selector(handleOpenStripChart:), 0); + make_menu_item(_menu, -1, "Open Flame Graph", @selector(handleOpenFlameGraph:)); + } + + [_menu addItem:[NSMenuItem separatorItem]]; + _time_items_end = 3; + + // Put a separator between time items and level items. + [_menu addItem:[NSMenuItem separatorItem]]; + _level_items_end = _time_items_end + 1; + + // For the main thread menu, also some options relating to all graph windows. + if (thread_index == 0) { + [_menu addItem:[NSMenuItem separatorItem]]; + make_menu_item(_menu, -1, "Close All Graphs", @selector(handleCloseAllGraphs:)); + make_menu_item(_menu, -1, "Reopen Default Graphs", @selector(handleReopenDefaultGraphs:)); + make_menu_item(_menu, -1, "Save Current Layout as Default", @selector(handleSaveDefaultGraphs:)); + } + + do_update(); +} + +/** + * + */ +MacStatsChartMenu:: +~MacStatsChartMenu() { + MacStatsChartMenuDelegate *delegate = (MacStatsChartMenuDelegate *)_menu.delegate; + [_menu release]; + [delegate release]; +} + +/** + * Adds the menu to the end of the indicated menu bar. + */ +void MacStatsChartMenu:: +add_to_menu(NSMenu *menu, int position) { + NSMenuItem *item = [[NSMenuItem alloc] init]; + [menu insertItem:item atIndex:position]; + [menu setSubmenu:_menu forItem:item]; + [item release]; +} + +/** + * Removes the menu from the menu bar. + */ +void MacStatsChartMenu:: +remove_from_menu(NSMenu *menu) { + int index = [menu indexOfItemWithSubmenu:_menu]; + if (index >= 0) { + [menu removeItemAtIndex:index]; + } +} + +/** + * Checks to see if the menu needs to be updated (e.g. because of new data + * from the client), and updates it if necessary. + */ +void MacStatsChartMenu:: +check_update() { + PStatView &view = _monitor->get_view(_thread_index); + if (view.get_level_index() != _last_level_index) { + do_update(); + } +} + +/** + * Unconditionally updates the menu with the latest data from the client. + */ +void MacStatsChartMenu:: +do_update() { + PStatView &view = _monitor->get_view(_thread_index); + _last_level_index = view.get_level_index(); + + const PStatClientData *client_data = _monitor->get_client_data(); + if (_thread_index != 0) { + std::string thread_name = client_data->get_thread_name(_thread_index); + _menu.title = [NSString stringWithUTF8String:thread_name.c_str()]; + } + + if (client_data->get_num_collectors() > _collector_items.size()) { + _collector_items.resize(client_data->get_num_collectors(), std::make_pair(nullptr, nullptr)); + } + + // The menu item(s) for the thread's frame time goes second. + const PStatViewLevel *view_level = view.get_top_level(); + if (_thread_index == 0) { + if (add_view(_menu, view_level, false, _time_items_end)) { + ++_time_items_end; + ++_level_items_end; + } + } else { + for (int c = 0; c < view_level->get_num_children(); ++c) { + if (add_view(_menu, view_level->get_child(c), false, _time_items_end)) { + ++_time_items_end; + ++_level_items_end; + } + } + } + + // And then the menu item(s) for each of the level values. + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, _thread_index)) { + + PStatView &level_view = _monitor->get_level_view(collector, _thread_index); + add_view(_menu, level_view.get_top_level(), true, _level_items_end); + } + } +} + +/** + * Adds a new entry or entries to the menu for the indicated view and its + * children. Returns true if an item was added, false if not. + */ +bool MacStatsChartMenu:: +add_view(NSMenu *parent_menu, const PStatViewLevel *view_level, + bool show_level, int insert_at) { + int collector = view_level->get_collector(); + + NSMenuItem *&menu_item = _collector_items[collector].first; + NSMenu *&menu = _collector_items[collector].second; + + const PStatClientData *client_data = _monitor->get_client_data(); + + int num_children = view_level->get_num_children(); + if (menu == nullptr && num_children == 0) { + // For a collector without children, no point in making a submenu. We just + // have the item open a strip chart directly (no point in creating a flame + // graph if there are no children). + if (menu_item != nullptr) { + // Already exists. + return false; + } + + std::string collector_name = client_data->get_collector_name(collector); + if (show_level) { + menu_item = make_menu_item(parent_menu, insert_at, + collector_name.c_str(), @selector(handleOpenStripChartLevel:), collector); + } else { + menu_item = make_menu_item(parent_menu, insert_at, + collector_name.c_str(), @selector(handleOpenStripChart:), collector); + } + return true; + } + else if (menu_item != nullptr && menu == nullptr) { + // Unhook the signal handler, we are creating a submenu. + menu_item.action = nil; + } + + // Create a submenu. + bool added_item = false; + if (menu_item == nullptr) { + std::string collector_name = client_data->get_collector_name(collector); + menu_item = make_menu_item(parent_menu, insert_at, collector_name.c_str(), nil); + added_item = true; + } + + if (menu == nullptr) { + menu = [[NSMenu alloc] init]; + [parent_menu setSubmenu:menu forItem:menu_item]; + + if (show_level) { + make_menu_item(menu, -1, "Open Strip Chart", + @selector(handleOpenStripChartLevel:), collector); + } else { + make_menu_item(menu, -1, "Open Strip Chart", + @selector(handleOpenStripChart:), collector); + + if (collector == 0) { + collector = -1; + } + + make_menu_item(menu, -1, "Open Flame Graph", + @selector(handleOpenFlameGraph:), collector); + } + + [menu addItem:[NSMenuItem separatorItem]]; + [menu release]; + } + + for (int c = 0; c < num_children; ++c) { + add_view(menu, view_level->get_child(c), show_level, 2 + !show_level); + } + + return added_item; +} + +/** + * + */ +NSMenuItem *MacStatsChartMenu:: +make_menu_item(NSMenu *parent_menu, int insert_at, const char *label, SEL action, int collector_index) { + NSMenuItem *menu_item = [[NSMenuItem alloc] init]; + menu_item.title = [NSString stringWithUTF8String:label]; + menu_item.target = _menu.delegate; + menu_item.action = action; + menu_item.tag = collector_index; + if (insert_at >= 0) { + [parent_menu insertItem:menu_item atIndex:insert_at]; + } else { + [parent_menu addItem:menu_item]; + } + [menu_item release]; + return menu_item; +} diff --git a/pandatool/src/mac-stats/macStatsChartMenuDelegate.h b/pandatool/src/mac-stats/macStatsChartMenuDelegate.h new file mode 100644 index 00000000..05f86302 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsChartMenuDelegate.h @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsChartMenuDelegate.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSCHARTMENUDELEGATE_H +#define MACSTATSCHARTMENUDELEGATE_H + +#import +#import + +class MacStatsMonitor; + +@interface MacStatsChartMenuDelegate : NSObject { + @private + MacStatsMonitor *_monitor; + int _thread_index; +} + +- (id)initWithMonitor:(MacStatsMonitor *)monitor threadIndex:(int)index; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsChartMenuDelegate.mm b/pandatool/src/mac-stats/macStatsChartMenuDelegate.mm new file mode 100644 index 00000000..67c12fca --- /dev/null +++ b/pandatool/src/mac-stats/macStatsChartMenuDelegate.mm @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsChartMenuDelegate.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsChartMenuDelegate.h" +#include "macStatsMonitor.h" + +@implementation MacStatsChartMenuDelegate + +- (id)initWithMonitor:(MacStatsMonitor *)monitor threadIndex:(int)index { + if (self = [super init]) { + _monitor = monitor; + _thread_index = index; + } + + return self; +} + +- (void)handleOpenTimeline:(NSMenuItem *)item { + _monitor->open_timeline(); +} + +- (void)handleOpenStripChart:(NSMenuItem *)item { + _monitor->open_strip_chart(_thread_index, item.tag, NO); +} + +- (void)handleOpenStripChartLevel:(NSMenuItem *)item { + _monitor->open_strip_chart(_thread_index, item.tag, YES); +} + +- (void)handleOpenFlameGraph:(NSMenuItem *)item { + _monitor->open_flame_graph(_thread_index, item.tag); +} + +- (void)handleOpenPianoRoll:(NSMenuItem *)item { + _monitor->open_piano_roll(_thread_index); +} + +- (void)handleCloseAllGraphs:(NSMenuItem *)item { + _monitor->close_all_graphs(); +} + +- (void)handleReopenDefaultGraphs:(NSMenuItem *)item { + _monitor->close_all_graphs(); + _monitor->open_default_graphs(); +} + +- (void)handleSaveDefaultGraphs:(NSMenuItem *)item { + _monitor->save_default_graphs(); +} + +@end diff --git a/pandatool/src/mac-stats/macStatsFlameGraph.h b/pandatool/src/mac-stats/macStatsFlameGraph.h new file mode 100644 index 00000000..a678e887 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsFlameGraph.h @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsFlameGraph.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSFLAMEGRAPH_H +#define MACSTATSFLAMEGRAPH_H + +#include "macStatsGraph.h" +#include "pStatFlameGraph.h" +#include "macStatsChartMenuDelegate.h" + +/** + * A window that draws a flame chart, which shows the collectors explicitly + * stopping and starting, one frame at a time. + */ +class MacStatsFlameGraph final : public PStatFlameGraph, public MacStatsGraph { +public: + MacStatsFlameGraph(MacStatsMonitor *monitor, int thread_index, + int collector_index=-1, int frame_number=-1); + virtual ~MacStatsFlameGraph(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + virtual NSMenu *get_label_menu(int collector_index) const; + +protected: + virtual void normal_guide_bars(); + + void clear_region(); + virtual void begin_draw(); + virtual void draw_bar(int depth, int from_x, int to_x, + int collector_index, int parent_index); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const; + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual bool handle_key(int graph_x, int graph_y, bool pressed, + UniChar c, unsigned short key_code); + virtual void handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual void handle_button_release(int graph_x, int graph_y); + virtual void handle_motion(int graph_x, int graph_y); + virtual void handle_leave(); + virtual void handle_wheel(int graph_x, int graph_y, double dx, double dy); + virtual void handle_draw_graph(CGContextRef ctx, NSRect rect); + virtual void handle_back(); + +public: + void handle_toggle_average(bool state); + +private: + int pixel_to_depth(int y) const; + void draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar); + void draw_guide_labels(CGContextRef ctx); + void draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar); + +private: + NSToolbarItem *_total_item; + + MacStatsChartMenuDelegate *_menu_delegate; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsFlameGraph.mm b/pandatool/src/mac-stats/macStatsFlameGraph.mm new file mode 100644 index 00000000..003629d4 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsFlameGraph.mm @@ -0,0 +1,919 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsFlameGraph.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsFlameGraph.h" +#include "macStatsMonitor.h" +#include "macStatsGraphView.h" +#include "macStatsScaleArea.h" +#include "pStatCollectorDef.h" +#include "cocoa_compat.h" + +@interface MacStatsFlameGraphViewController : MacStatsGraphViewController +@end + +static const int default_flame_graph_width = 800; +static const int default_flame_graph_height = 250; + +/** + * + */ +MacStatsFlameGraph:: +MacStatsFlameGraph(MacStatsMonitor *monitor, int thread_index, + int collector_index, int frame_number) : + PStatFlameGraph(monitor, thread_index, collector_index, frame_number, 0, 0), + MacStatsGraph(monitor, [MacStatsFlameGraphViewController alloc]) +{ + // Used for popup menus. + _menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index]; + + // Set the initial size of the graph. + int height = default_flame_graph_height + _window.frame.size.height - _window.contentLayoutRect.size.height; + _graph_view.frame = NSMakeRect(0, 0, default_flame_graph_width, height); + _graph_view_controller.view.frame = NSMakeRect(0, 0, default_flame_graph_width, height); + + _total_item = nil; + if (@available(macOS 11.0, *)) { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""]; + toolbar.delegate = _graph_view_controller; + toolbar.displayMode = NSToolbarDisplayModeIconOnly; + _window.toolbar = toolbar; + [_window setToolbarStyle:NSWindowToolbarStyleUnifiedCompact]; + + for (NSToolbarItem *item in toolbar.items) { + if ([item.itemIdentifier isEqual:@"total"]) { + _total_item = item; + break; + } + } + [toolbar release]; + } + + //MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this]; + //scale_area_controller.layoutAttribute = NSLayoutAttributeRight; + //_scale_area = scale_area_controller.view; + //[_window addTitlebarAccessoryViewController:scale_area_controller]; + + _window.contentViewController = _graph_view_controller; + + _graph_view_controller.backToolbarItemVisible = NO; + + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + if (get_average_mode()) { + start_animation(); + } + + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + + if (@available(macOS 11.0, *)) { + std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name()); + [_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]]; + } + + [_window makeKeyAndOrderFront:nil]; +} + +/** + * + */ +MacStatsFlameGraph:: +~MacStatsFlameGraph() { + [_menu_delegate release]; +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void MacStatsFlameGraph:: +new_collector(int collector_index) { + MacStatsGraph::new_collector(collector_index); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void MacStatsFlameGraph:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + + if (!_pause) { + update(); + + if (@available(macOS 11.0, *)) { + std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name()); + [_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]]; + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void MacStatsFlameGraph:: +force_redraw() { + if (_ctx) { + PStatFlameGraph::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void MacStatsFlameGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatFlameGraph::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void MacStatsFlameGraph:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + if (@available(macOS 11.0, *)) { + std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name()); + [_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]]; + } + + //_scale_area.needsDisplay = YES; + } +} + +/** + * Called when the user single-clicks on a label. + */ +void MacStatsFlameGraph:: +on_click_label(int collector_index) { + int current = get_collector_index(); + if (collector_index != current) { + if (get_history_depth() == 0) { + _graph_view_controller.backToolbarItemVisible = YES; + } + push_collector_index(collector_index); + + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } +} + +/** + * Called when the user hovers the mouse over a label. + */ +void MacStatsFlameGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void MacStatsFlameGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Called when the mouse right-clicks on a label, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsFlameGraph:: +get_label_menu(int collector_index) const { + NSMenu *menu = [[[NSMenu alloc] init] autorelease]; + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + if (@available(macOS 14.0, *)) { + [menu addItem:[NSMenuItem sectionHeaderWithTitle:[NSString stringWithUTF8String:label.c_str()]]]; + } else { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label.c_str()] action:nil keyEquivalent:@""]; + item.enabled = NO; + [menu addItem:item]; + [item release]; + } + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Set as Focus" action:@selector(handleSetAsFocus:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + item.enabled = (collector_index != 0 || get_collector_index() != 0); + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:@selector(handleOpenStripChart:) keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Change Color\u2026" action:@selector(handleChangeColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Reset Color" action:@selector(handleResetColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + return menu; +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void MacStatsFlameGraph:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + int num_bars = get_xsize() / 100; + + _guide_bars.clear(); + + double dist = get_horizontal_scale() / num_bars; + + for (int i = 1; i < num_bars; ++i) { + _guide_bars.push_back(make_guide_bar(i * dist)); + } + + _guide_bars_changed = true; + + //nassertv_always(_scale_area != nullptr); + //_scale_area.needsDisplay = YES; +} + +/** + * Erases the chart area. + */ +void MacStatsFlameGraph:: +clear_region() { + if (_ctx) { + CGContextSetFillColorWithColor(_ctx, _background_color); + CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize())); + } +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void MacStatsFlameGraph:: +begin_draw() { + if (!_ctx) { + return; + } + + clear_region(); + + // isFlipped is true in the NSView, so flip the text again + CGContextSetTextMatrix(_ctx, CGAffineTransformMakeScale(1, -1)); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + const GuideBar &bar = get_guide_bar(i); + draw_guide_bar(_ctx, bar); + draw_guide_label(_ctx, bar); + } +} + +/** + * Should be overridden by the user class. Should draw a single bar at the + * indicated location. + */ +void MacStatsFlameGraph:: +draw_bar(int depth, int from_x, int to_x, int collector_index, int parent_index) { + double bottom = get_ysize() - depth * 4.000 * 5; + double top = bottom - 4.000 * 5; + + top += 1; + + MacStatsMonitor *monitor = MacStatsGraph::_monitor; + + bool is_highlighted = collector_index == _highlighted_index; + CGContextSetFillColorWithColor(_ctx, + monitor->get_collector_color(collector_index, is_highlighted)); + + if (to_x < from_x + 3) { + // It's just a tiny sliver. This is a more reliable way to draw it. + CGRect rect = CGRectMake(from_x, top, to_x - from_x, bottom - top); + CGContextFillRect(_ctx, rect); + } + else { + double radius = std::min((double)4.000, (to_x - from_x) / 2.0); + CGContextBeginPath(_ctx); + CGContextAddArc(_ctx, to_x - radius, top + radius, radius, -0.5 * M_PI, 0.0, NO); + CGContextAddArc(_ctx, to_x - radius, bottom - radius, radius, 0.0, 0.5 * M_PI, NO); + CGContextAddArc(_ctx, from_x + radius, bottom - radius, radius, 0.5 * M_PI, M_PI, NO); + CGContextAddArc(_ctx, from_x + radius, top + radius, radius, M_PI, 1.5 * M_PI, NO); + CGContextClosePath(_ctx); + CGContextFillPath(_ctx); + + if ((to_x - from_x) >= 4.000 * 4) { + // Only bother drawing the text if we've got some space to draw on. + int left = std::max(from_x, 0) + 4.000 / 2; + int right = std::min(to_x, get_xsize()) - 4.000 / 2; + + const PStatClientData *client_data = monitor->get_client_data(); + const PStatCollectorDef &def = client_data->get_collector_def(collector_index); + + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + monitor->get_collector_text_color(collector_index, is_highlighted), + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, def._name.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CGRect bounds = CTLineGetImageBounds(line, _ctx); + CFRelease(astr); + CFRelease(str); + + if (bounds.size.width < right - left) { + // We have room for more. Show the collector's actual parent, if it's + // different than the block it's shown above. + if (def._parent_index > 0 && def._parent_index != parent_index) { + const PStatCollectorDef &parent_def = client_data->get_collector_def(def._parent_index); + std::string long_name = parent_def._name + ":" + def._name; + + CFStringRef long_str = CFStringCreateWithCString(kCFAllocatorDefault, long_name.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef long_astr = CFAttributedStringCreate(kCFAllocatorDefault, long_str, attribs); + + CTLineRef long_line = CTLineCreateWithAttributedString((CFAttributedStringRef)long_astr); + CGRect long_bounds = CTLineGetImageBounds(long_line, _ctx); + + if (long_bounds.size.width < right - left) { + CFRelease(line); + line = long_line; + bounds = long_bounds; + } else { + CFRelease(long_line); + } + CFRelease(long_astr); + CFRelease(long_str); + } + } + else { + static CFStringRef token_str = CFSTR("\u2026"); + CFAttributedStringRef token_astr = CFAttributedStringCreate(kCFAllocatorDefault, token_str, attribs); + CTLineRef token_line = CTLineCreateWithAttributedString(token_astr); + CTLineRef trunc_line = CTLineCreateTruncatedLine(line, right - left, kCTLineTruncationEnd, token_line); + CFRelease(line); + CFRelease(token_astr); + CFRelease(token_line); + line = trunc_line; + } + + // Center the text vertically in the bar. + if (line != nullptr) { + CGContextSetTextPosition(_ctx, left, top + (bottom - top + bounds.size.height) / 2); + CTLineDraw(line, _ctx); + CFRelease(line); + } + CFRelease(attribs); + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void MacStatsFlameGraph:: +end_draw() { + _graph_view.needsDisplay = YES; +} + +/** + * Called at the end of the draw cycle. + */ +void MacStatsFlameGraph:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool MacStatsFlameGraph:: +animate(double time, double dt) { + return PStatFlameGraph::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool MacStatsFlameGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + MacStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void MacStatsFlameGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + MacStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * Called when the mouse right-clicks on the graph, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsFlameGraph:: +get_graph_menu(int mouse_x, int mouse_y) const { + int collector_index = get_bar_collector(pixel_to_depth(mouse_y), mouse_x); + if (collector_index != -1) { + return get_label_menu(collector_index); + } + return nil; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsFlameGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return get_bar_tooltip(pixel_to_depth(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +MacStatsGraph::DragMode MacStatsFlameGraph:: +consider_drag_start(int graph_x, int graph_y) { + if (graph_y >= 0 && graph_y < get_ysize()) { + if (graph_x >= 0 && graph_x < get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = graph_x; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + return DM_none; +} + +/** + * + */ +bool MacStatsFlameGraph:: +handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) { + bool changed = false; + + if (pressed) { + switch (c) { + case NSLeftArrowFunctionKey: + changed = prev_frame(); + break; + + case NSRightArrowFunctionKey: + changed = next_frame(); + break; + + case NSHomeFunctionKey: + changed = first_frame(); + break; + + case NSEndFunctionKey: + changed = last_frame(); + break; + } + } + + if (changed) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + + return changed; +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +void MacStatsFlameGraph:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + int depth = pixel_to_depth(graph_y); + int collector_index = get_bar_collector(depth, graph_x); + if (double_click && button == 0) { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + if (collector_index >= 0) { + on_click_label(collector_index); + } else { + if (get_history_depth() > 0) { + clear_history(); + _graph_view_controller.backToolbarItemVisible = NO; + } + set_collector_index(-1); + + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + return; + } + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_x); + // SetCapture(_graph_window); + return; + } + else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_x = graph_x; + // SetCapture(_graph_window); + return; + } + + return MacStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +void MacStatsFlameGraph:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return MacStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsFlameGraph:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int depth = pixel_to_depth(graph_y); + int collector_index = get_bar_collector(depth, graph_x); + on_enter_label(collector_index); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_x >= 0 && graph_x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x)); + return; + } + } + else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + return; + } + + return MacStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +void MacStatsFlameGraph:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + return; +} + +/** + * + */ +void MacStatsFlameGraph:: +handle_wheel(int graph_x, int graph_y, double dx, double dy) { + if (dx != 0.0) { + if ((dx > 0.0) ? prev_frame() : next_frame()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + } +} + +/** + * Fills in the graph window. + */ +void MacStatsFlameGraph:: +handle_draw_graph(CGContextRef ctx, NSRect rect) { + MacStatsGraph::handle_draw_graph(ctx, rect); + + CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1, -1)); + + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + const GuideBar &bar = get_user_guide_bar(i); + draw_guide_bar(ctx, bar); + draw_guide_label(ctx, bar); + } +} + +/** + * Called when the mouse clicks the back button in the toolbar. + */ +void MacStatsFlameGraph:: +handle_back() { + if (pop_collector_index()) { + if (get_history_depth() == 0) { + _graph_view_controller.backToolbarItemVisible = NO; + } + + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } +} + +/** + * Called when the mouse toggles the "Average" checkbox in the toolbar. + */ +void MacStatsFlameGraph:: +handle_toggle_average(bool state) { + set_average_mode(state); + if (state) { + start_animation(); + } +} + + +/** + * Converts a pixel to a depth index. + */ +int MacStatsFlameGraph:: +pixel_to_depth(int y) const { + return (get_ysize() - 1 - y) / (4.000 * 5); +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void MacStatsFlameGraph:: +draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to the top. + CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor); + /*switch (bar._style) { + case GBS_target: + CGContextSetRGBStrokeColor(ctx, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2], 1.0); + break; + + case GBS_user: + CGContextSetRGBStrokeColor(ctx, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2], 1.0); + break; + + default: + CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0); + break; + }*/ + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, x, 0); + CGContextAddLineToPoint(ctx, x, get_ysize()); + CGContextStrokePath(ctx); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void MacStatsFlameGraph:: +draw_guide_labels(CGContextRef ctx) { + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_label(ctx, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_label(ctx, get_user_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void MacStatsFlameGraph:: +draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) { + NSColor *color; + color = [NSColor tertiaryLabelColor]; + /*switch (bar._style) { + case GBS_target: + color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0]; + break; + + case GBS_user: + color = [NSColor colorWithDeviceRed:rgb_user_guide_bar[0] green:rgb_user_guide_bar[1] blue:rgb_user_guide_bar[2] alpha:1.0]; + break; + + default: + color = [NSColor colorWithDeviceRed:rgb_dark_gray[0] green:rgb_dark_gray[1] blue:rgb_dark_gray[2] alpha:1.0]; + break; + }*/ + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + color, + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, label.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + CFRelease(attribs); + CFRelease(str); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CFRelease(astr); + CGRect bounds = CTLineGetImageBounds(line, ctx); + int width = bounds.size.width; + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - width); + double to_height = pixel_to_height(x + width); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + CFRelease(line); + return; + } + } + + if (x >= 0 && x < get_xsize()) { + int y = bounds.size.height; + if (@available(macOS 11.0, *)) { + // Account for underlap of title bar + y += _window.frame.size.height - _window.contentLayoutRect.size.height; + } + CGContextSetTextPosition(ctx, x + 6, y + 6); + CTLineDraw(line, ctx); + } + + CFRelease(line); +} + +@implementation MacStatsFlameGraphViewController + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { + return @[@"back", @"average", @"total"]; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return @[@"average", @"total"]; +} + +- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar + itemForItemIdentifier:(NSString *)ident + willBeInsertedIntoToolbar:(BOOL)flag { + + if (@available(macOS 11.0, *)) { + if ([ident isEqual:@"average"]) { + NSButton *button = [NSButton buttonWithTitle:@"Average" target:self action:@selector(handleToggleAverage:)]; + button.image = [NSImage imageWithSystemSymbolName:@"sum" accessibilityDescription:@""]; + button.bezelStyle = NSBezelStyleTexturedRounded; + button.buttonType = NSButtonTypePushOnPushOff; + button.bordered = YES; + button.toolTip = @"Average"; + button.state = NSOffState; + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease]; + item.label = @"Average"; + item.view = button; + return item; + } + if ([ident isEqual:@"total"]) { + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease]; + item.label = @"Total"; + item.enabled = NO; + [item setBordered:YES]; + return item; + } + } + + return [super toolbar:toolbar itemForItemIdentifier:ident willBeInsertedIntoToolbar:flag]; +} + +- (void)handleToggleAverage:(NSButton *)button { + MacStatsFlameGraph *graph = (MacStatsFlameGraph *)_graph; + graph->handle_toggle_average(button.state == NSOnState); +} + +@end diff --git a/pandatool/src/mac-stats/macStatsGraph.h b/pandatool/src/mac-stats/macStatsGraph.h new file mode 100644 index 00000000..2905f60d --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraph.h @@ -0,0 +1,139 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraph.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATSGRAPH_H +#define MACSTATSGRAPH_H + +#include "pandatoolbase.h" +#include "macStatsGraphViewController.h" +#include "macStatsLabelStack.h" +#include "pmap.h" +#include "luse.h" + +class MacStatsMonitor; + +/** + * This is just an abstract base class to provide a common pointer type for + * the various kinds of graphs that may be created for a MacStatsMonitor. + */ +class MacStatsGraph { +public: + // What is the user adjusting by dragging the mouse in a window? + enum DragMode { + DM_none, + DM_scale, + DM_guide_bar, + DM_new_guide_bar, + DM_sizing, + DM_pan, + }; + +public: + MacStatsGraph(MacStatsMonitor *monitor, MacStatsGraphViewController *controller); + virtual ~MacStatsGraph(); + + void close(); + + MacStatsMonitor *get_monitor() { return _monitor; } + NSSplitView *get_split_view() { return _split_view; } + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw()=0; + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void user_guide_bars_changed(); + virtual void on_click_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + virtual NSMenu *get_label_menu(int collector_index) const; + virtual std::string get_label_tooltip(int collector_index) const; + + void reset_collector_color(int collector_index); + +protected: + void start_animation(); + virtual bool animate(double time, double dt); + + void get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + +public: + // These must be public, because we can't declare Objective-C friends here. + virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const; + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + virtual void set_drag_mode(DragMode drag_mode); + + virtual bool handle_key(int graph_x, int graph_y, bool pressed, + UniChar c, unsigned short key_code); + virtual void handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual void handle_button_release(int graph_x, int graph_y); + virtual void handle_motion(int graph_x, int graph_y); + virtual void handle_leave(); + virtual void handle_scroll(); + virtual void handle_wheel(int graph_x, int graph_y, double dx, double dy); + virtual void handle_magnify(int graph_x, int graph_y, double scale); + virtual void handle_draw_graph(CGContextRef ctx, NSRect rect); + virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect); + virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect); + virtual void handle_back(); + virtual void handle_timer(); + +protected: + CGColorRef _background_color; + + MacStatsMonitor *_monitor; + NSWindow *_window; + NSView *_graph_view; + MacStatsGraphViewController *_graph_view_controller; + NSView *_scale_area = nullptr; + NSSplitView *_split_view = nullptr; + NSScrollView *_sidebar_view = nullptr; + MacStatsLabelStack _label_stack; + + CGContextRef _ctx = nullptr; + int _bitmap_xsize, _bitmap_ysize; + + DragMode _drag_mode; + DragMode _potential_drag_mode; + int _drag_start_x, _drag_start_y; + double _drag_scale_start; + int _drag_guide_bar; + + int _highlighted_index = -1; + + bool _pause; + + NSTimer *_animation_timer = nil; + double _time = 0.0; + + static const CGFloat rgb_white[4]; + static const CGFloat rgb_light_gray[4]; + static const CGFloat rgb_dark_gray[4]; + static const CGFloat rgb_black[4]; + static const CGFloat rgb_user_guide_bar[4]; + +private: + void setup_bitmap(int xsize, int ysize, double scale); + void release_bitmap(); +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsGraph.mm b/pandatool/src/mac-stats/macStatsGraph.mm new file mode 100644 index 00000000..466d875d --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraph.mm @@ -0,0 +1,491 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraph.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "macStatsGraph.h" +#include "macStatsGraphView.h" +#include "macStatsMonitor.h" +#include "macStatsLabelStack.h" + +const CGFloat MacStatsGraph::rgb_light_gray[4] = { + 0x9a / (CGFloat)0xff, 0x9a / (CGFloat)0xff, 0x9a / (CGFloat)0xff, 1.0, +}; +const CGFloat MacStatsGraph::rgb_dark_gray[4] = { + 0xb0 / (CGFloat)0xff, 0xb0 / (CGFloat)0xff, 0xb0 / (CGFloat)0xff, 1.0, +}; +const CGFloat MacStatsGraph::rgb_user_guide_bar[4] = { + 0x82 / (CGFloat)0xff, 0x96 / (CGFloat)0xff, 0xff / (CGFloat)0xff, 1.0, +}; + +static const NSInteger style_mask = NSWindowStyleMaskTitled + | NSWindowStyleMaskClosable + | NSWindowStyleMaskMiniaturizable + | NSWindowStyleMaskResizable; + +/** + * + */ +MacStatsGraph:: +MacStatsGraph(MacStatsMonitor *monitor, MacStatsGraphViewController *controller) : + _monitor(monitor) +{ + _background_color = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 0.0); + + _drag_mode = DM_none; + _potential_drag_mode = DM_none; + _drag_scale_start = 0.0f; + + _pause = false; + + NSInteger this_style_mask = style_mask; + if (@available(macOS 11.0, *)) { + this_style_mask |= NSWindowStyleMaskFullSizeContentView; + } + + _window = [NSWindow alloc]; + [_window initWithContentRect:NSMakeRect(100, 500, 500, 150) + styleMask:this_style_mask + backing:NSBackingStoreBuffered + defer:NO]; + _window.releasedWhenClosed = NO; + _window.titlebarAppearsTransparent = NO; + _window.excludedFromWindowsMenu = NO; + + _window.contentMinSize = NSMakeSize(68, _window.contentView.frame.size.height - _window.contentLayoutRect.size.height); + + _graph_view_controller = [controller initWithGraph:this]; + _graph_view = ((MacStatsGraphViewController *)_graph_view_controller).graphView; + + // Get notified when the window closes. + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:_graph_view_controller + selector:@selector(windowWillClose:) + name:NSWindowWillCloseNotification + object:_window]; +} + +/** + * + */ +MacStatsGraph:: +~MacStatsGraph() { + if (_animation_timer != nil) { + [_animation_timer invalidate]; + [_animation_timer release]; + _animation_timer = nil; + } + + _monitor = nullptr; + release_bitmap(); + + _label_stack.clear_labels(); + + [_graph_view_controller release]; + [_window release]; + + CGColorRelease(_background_color); +} + +/** + * + */ +void MacStatsGraph:: +close() { + [_window close]; +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void MacStatsGraph:: +new_collector(int new_collector) { +} + +/** + * Called whenever new data arrives. + */ +void MacStatsGraph:: +new_data(int thread_index, int frame_number) { +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void MacStatsGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void MacStatsGraph:: +set_time_units(int unit_mask) { +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void MacStatsGraph:: +set_scroll_speed(double scroll_speed) { +} + +/** + * Changes the pause flag for the graph. When this flag is true, the graph + * does not update in response to new data. + */ +void MacStatsGraph:: +set_pause(bool pause) { + _pause = pause; +} + +/** + * Called when the user guide bars have been changed. + */ +void MacStatsGraph:: +user_guide_bars_changed() { + if (_scale_area != nullptr) { + _scale_area.needsDisplay = YES; + } + _graph_view.needsDisplay = YES; +} + +/** + * Called when the user single-clicks on a label. + */ +void MacStatsGraph:: +on_click_label(int collector_index) { +} + +/** + * Called when the user hovers the mouse over a label. + */ +void MacStatsGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + force_redraw(); + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void MacStatsGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + force_redraw(); + } +} + +/** + * Called when the mouse right-clicks on a label, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsGraph:: +get_label_menu(int collector_index) const { + return nil; +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsGraph:: +get_label_tooltip(int collector_index) const { + return std::string(); +} + +/** + * Turns on the animation timer, if it hasn't already been turned on. + */ +void MacStatsGraph:: +start_animation() { + if (_animation_timer != nil) { + return; + } + + _time = 0.0; + _animation_timer = [NSTimer scheduledTimerWithTimeInterval:1 / 60.0 target:_graph_view selector:@selector(handleTimer:) userInfo:nil repeats:YES]; + [_animation_timer retain]; +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool MacStatsGraph:: +animate(double time, double dt) { + return false; +} + +/** + * Returns the current window dimensions. + */ +void MacStatsGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + + NSRect screen_frame = _window.screen.visibleFrame; + + NSRect frame = _window.frame; + x = frame.origin.x - screen_frame.origin.x; + y = (screen_frame.origin.y + screen_frame.size.height) - (frame.origin.y + frame.size.height); + width = frame.size.width; + height = frame.size.height; + maximized = _window.zoomed; + minimized = _window.miniaturized; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void MacStatsGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + + NSRect screen_frame = _window.screen.visibleFrame; + + NSRect frame; + frame.origin.x = screen_frame.origin.x + x; + frame.origin.y = (screen_frame.origin.y + screen_frame.size.height) - (y + height); + frame.size.width = width; + frame.size.height = height; + [_window setFrame:frame display:NO]; + + if (maximized != _window.zoomed) { + [_window zoom:_window]; + } + + if (minimized != _window.miniaturized) { + if (minimized) { + [_window miniaturize:_window]; + } else { + [_window deminiaturize:_window]; + } + } +} + +/** + * Called when the given collector has changed colors. + */ +void MacStatsGraph:: +reset_collector_color(int collector_index) { + force_redraw(); + _label_stack.update_label_color(collector_index); +} + +/** + * Called when the mouse right-clicks on the graph, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsGraph:: +get_graph_menu(int mouse_x, int mouse_y) const { + return nil; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +MacStatsGraph::DragMode MacStatsGraph:: +consider_drag_start(int graph_x, int graph_y) { + return DM_none; +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void MacStatsGraph:: +set_drag_mode(MacStatsGraph::DragMode drag_mode) { + _drag_mode = drag_mode; +} + +/** + * + */ +bool MacStatsGraph:: +handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) { + return false; +} + +/** + * Called when the mouse button is depressed within the window, or any nested + * window. + */ +void MacStatsGraph:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (_potential_drag_mode != DM_none && button == 1) { + set_drag_mode(_potential_drag_mode); + _drag_start_x = graph_x; + _drag_start_y = graph_y; + // SetCapture(_window); + } +} + +/** + * Called when the mouse button is released within the window, or any nested + * window. + */ +void MacStatsGraph:: +handle_button_release(int graph_x, int graph_y) { + set_drag_mode(DM_none); + // ReleaseCapture(); + + return handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the window, or any nested window. + */ +void MacStatsGraph:: +handle_motion(int graph_x, int graph_y) { + _potential_drag_mode = consider_drag_start(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +void MacStatsGraph:: +handle_leave() { +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsGraph:: +handle_scroll() { + force_redraw(); +} + +/** + * + */ +void MacStatsGraph:: +handle_wheel(int graph_x, int graph_y, double dx, double dy) { +} + +/** + * + */ +void MacStatsGraph:: +handle_magnify(int graph_x, int graph_y, double scale) { +} + +/** + * + */ +void MacStatsGraph:: +handle_timer() { + _time += 1.0 / 60.0; + + if (!animate(_time, 1.0 / 60.0)) { + [_animation_timer invalidate]; + [_animation_timer release]; + _animation_timer = nil; + } +} + +/** + * Fills in the graph window. + */ +void MacStatsGraph:: +handle_draw_graph(CGContextRef ctx, NSRect rect) { + CGContextSetBlendMode(ctx, kCGBlendModeCopy); + CGContextSetInterpolationQuality(ctx, kCGInterpolationNone); + + // Quantize this so that changed_graph_size will always call force_redraw() + NSRect full_rect = _graph_view.bounds; + full_rect.size.width = (int)full_rect.size.width; + full_rect.size.height = (int)full_rect.size.height; + + CGSize size = CGContextConvertSizeToDeviceSpace(ctx, full_rect.size); + int width = abs((int)size.width); + int height = abs((int)size.height); + + if (_ctx == nullptr || _bitmap_xsize != width || _bitmap_ysize != height) { + if (_ctx == nullptr && _scale_area != nullptr) { + _scale_area.needsDisplay = YES; + } + setup_bitmap(width, height, _window.backingScaleFactor); + + changed_graph_size(full_rect.size.width, full_rect.size.height); + } + + CGImageRef image = CGBitmapContextCreateImage(_ctx); + CGContextDrawImage(ctx, CGRectMake(0, 0, full_rect.size.width, full_rect.size.height), image); + CGImageRelease(image); +} + +/** + * Fills in the graph window overhang, which is the area outside the graph + * bounds that may become visible momentarily due to scroll elasticity. + */ +void MacStatsGraph:: +handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) { +} + +/** + * Fills in the scale area. + */ +void MacStatsGraph:: +handle_draw_scale_area(CGContextRef ctx, NSRect rect) { +} + +/** + * Called when the mouse clicks the back button in the toolbar. + */ +void MacStatsGraph:: +handle_back() { +} + +/** + * Sets up a backing-store bitmap of the indicated size. + */ +void MacStatsGraph:: +setup_bitmap(int xsize, int ysize, double scale) { + release_bitmap(); + + _bitmap_xsize = xsize; + _bitmap_ysize = ysize; + + // Ostensibly, a layer context is more efficient, but I tried it and the + // performance was horrible compared to a bitmap context. + _ctx = CGBitmapContextCreate(nullptr, xsize, ysize, 8, 0, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), kCGImageAlphaPremultipliedLast); + CGContextSetBlendMode(_ctx, kCGBlendModeCopy); + CGContextScaleCTM(_ctx, scale, scale); +} + +/** + * Frees the backing-store bitmap created by setup_bitmap(). + */ +void MacStatsGraph:: +release_bitmap() { + if (_ctx != nullptr) { + CGContextRelease(_ctx); + _ctx = nullptr; + } +} diff --git a/pandatool/src/mac-stats/macStatsGraphView.h b/pandatool/src/mac-stats/macStatsGraphView.h new file mode 100644 index 00000000..efd0d0c6 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraphView.h @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraphView.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSGRAPHVIEW_H +#define MACSTATSGRAPHVIEW_H + +#include "cocoa_compat.h" + +class MacStatsGraph; + +@interface MacStatsGraphView : NSView { + @public + MacStatsGraph *_graph; +} + +- (id)initWithGraph:(MacStatsGraph *)graph; +- (void)drawRect:(NSRect)dirtyRect; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsGraphView.mm b/pandatool/src/mac-stats/macStatsGraphView.mm new file mode 100644 index 00000000..82b2d5d8 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraphView.mm @@ -0,0 +1,156 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraphView.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsGraphView.h" +#include "macStatsGraph.h" +#include "macStatsStripChart.h" +#include "macStatsFlameGraph.h" +#include "macStatsTimeline.h" +#include "macStatsScaleArea.h" + +@implementation MacStatsGraphView + +- (id)initWithGraph:(MacStatsGraph *)graph { + if (self = [super init]) { + _graph = graph; + + self.translatesAutoresizingMaskIntoConstraints = NO; + + NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil]; + [self addTrackingArea:area]; + [area release]; + + [self addToolTipRect:NSMakeRect(0, 0, 1000, 1000) owner:self userData:nil]; + } + + return self; +} + +- (BOOL)isFlipped { + return YES; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)keyDown:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + UniChar c = 0; + NSString *str = [event charactersIgnoringModifiers]; + if (str != nil && str.length == 1) { + c = [str characterAtIndex:0]; + } + _graph->handle_key(pos.x, pos.y, true, c, event.keyCode); +} + +- (void)keyUp:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + UniChar c = 0; + NSString *str = [event charactersIgnoringModifiers]; + if (str != nil && str.length == 1) { + c = [str characterAtIndex:0]; + } + _graph->handle_key(pos.x, pos.y, false, c, event.keyCode); +} + +- (void)mouseDown:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_button_press(pos.x, pos.y, event.clickCount > 1, event.buttonNumber); +} + +- (void)mouseUp:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_button_release(pos.x, pos.y); +} + +- (void)mouseDragged:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_motion(pos.x, pos.y); +} + +- (void)mouseMoved:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_motion(pos.x, pos.y); +} + +- (void)mouseExited:(NSEvent *)event { + _graph->handle_leave(); +} + +- (void)scrollWheel:(NSEvent *)event { + [super scrollWheel:event]; + if (event.deltaX != 0) { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_wheel(pos.x, pos.y, event.deltaX, event.deltaY); + } +} + +- (void)magnifyWithEvent:(NSEvent *)event { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + _graph->handle_magnify(pos.x, pos.y, event.magnification); +} + +- (void)handleTimer:(NSTimer *)timer { + _graph->handle_timer(); +} + +- (void)viewDidChangeEffectiveAppearance { + if (_graph != nullptr) { + // Don't call this initially + if (self.window != nil) { + [NSAppearance setCurrentAppearance:self.effectiveAppearance]; + _graph->force_redraw(); + } + } +} + +- (void)drawRect:(NSRect)dirtyRect { + if (_graph != nullptr) { + CGContextRef ctx = [NSGraphicsContext currentContext].CGContext; + _graph->handle_draw_graph(ctx, dirtyRect); + } +} + +// Not called when building with macOS 14 SDK due to change in clipsToBounds +// (but there it simply calls drawRect with the overhang region) +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 140000 +- (void)drawBackgroundOverhangInRect:(NSRect)dirtyRect { + if (_graph != nullptr) { + CGContextRef ctx = [NSGraphicsContext currentContext].CGContext; + _graph->handle_draw_graph_overhang(ctx, dirtyRect); + } +} +#endif + +- (NSMenu *)menuForEvent:(NSEvent *)event { + if (_graph != nullptr) { + NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil]; + return _graph->get_graph_menu(pos.x, pos.y); + } + return nil; +} + +- (NSString *)view:(NSView *)view + stringForToolTip:(NSToolTipTag)tag + point:(NSPoint)point + userData:(void *)data { + + if (_graph != nullptr) { + std::string text = _graph->get_graph_tooltip(point.x, point.y); + return [NSString stringWithUTF8String:text.c_str()]; + } + return @""; +} + +@end diff --git a/pandatool/src/mac-stats/macStatsGraphViewController.h b/pandatool/src/mac-stats/macStatsGraphViewController.h new file mode 100644 index 00000000..d688a656 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraphViewController.h @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraphViewController.h + * @author rdb + * @date 2023-08-28 + */ + +#ifndef MACSTATSGRAPHVIEWCONTROLLER_H +#define MACSTATSGRAPHVIEWCONTROLLER_H + +#include "macStatsGraphView.h" + +#import + +class MacStatsGraph; + +@interface MacStatsGraphViewController : NSViewController { + @protected + MacStatsGraph *_graph; +} + +- (id)initWithGraph:(MacStatsGraph *)graph; +- (MacStatsGraphView *)graphView; +- (BOOL)backToolbarItemVisible; +- (void)setBackToolbarItemVisible:(BOOL)show; + +@end + +@interface MacStatsScrollableGraphViewController : MacStatsGraphViewController + +- (MacStatsGraphView *)graphView; +- (NSClipView *)clipView; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsGraphViewController.mm b/pandatool/src/mac-stats/macStatsGraphViewController.mm new file mode 100644 index 00000000..84f450f7 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsGraphViewController.mm @@ -0,0 +1,224 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsGraphViewController.mm + * @author rdb + * @date 2023-08-28 + */ + +#include "macStatsGraphViewController.h" +#include "macStatsGraph.h" +#include "macStatsMonitor.h" +#include "cocoa_compat.h" + +@implementation MacStatsGraphViewController + +- (id)initWithGraph:(MacStatsGraph *)graph { + if (self = [super init]) { + _graph = graph; + } + + return self; +} + +- (void)windowWillClose:(NSNotification *)notification { + MacStatsGraph *graph = _graph; + if (graph != nullptr) { + MacStatsMonitor *monitor = graph->get_monitor(); + if (monitor != nullptr) { + _graph = nullptr; + monitor->remove_graph(graph); + } + } +} + +- (void)loadView { + NSView *graph_view = [[MacStatsGraphView alloc] initWithGraph:_graph]; + NSView *background; + if (@available(macOS 10.14, *)) { + NSVisualEffectView *effect_view = [[NSVisualEffectView alloc] init]; + effect_view.material = (NSVisualEffectMaterial)18;//NSVisualEffectMaterialContentBackground; + background = effect_view; + } else { + background = [[NSView alloc] init]; + background.wantsLayer = YES; + background.layer.backgroundColor = [NSColor controlBackgroundColor].CGColor; + } + [background addSubview:graph_view]; + self.view = background; + + [graph_view.widthAnchor constraintEqualToAnchor:background.widthAnchor].active = YES; + [graph_view.heightAnchor constraintEqualToAnchor:background.heightAnchor].active = YES; + [graph_view release]; + [background release]; +} + +- (MacStatsGraphView *)graphView { + return (MacStatsGraphView *)self.view.subviews[0]; +} + +- (void)handleSplitViewResize:(NSNotification *)notification { + NSSplitView *split_view = (NSSplitView *)notification.object; + NSWindow *window = split_view.window; + NSToolbar *toolbar = window.toolbar; + if ([split_view isSubviewCollapsed:split_view.arrangedSubviews[0]]) { + if (toolbar.items[0].itemIdentifier != NSToolbarToggleSidebarItemIdentifier) { + [toolbar insertItemWithItemIdentifier:NSToolbarToggleSidebarItemIdentifier atIndex:0]; + } + } else { + if (toolbar.items[0].itemIdentifier == NSToolbarToggleSidebarItemIdentifier) { + [toolbar removeItemAtIndex:0]; + } + } +} + +- (BOOL)backToolbarItemVisible { + NSToolbar *toolbar = self.view.window.toolbar; + if ([toolbar.items[0].itemIdentifier isEqual:@"back"] || + [toolbar.items[1].itemIdentifier isEqual:@"back"]) { + return YES; + } else { + return NO; + } +} + +- (void)setBackToolbarItemVisible:(BOOL)show { + NSToolbar *toolbar = self.view.window.toolbar; + if ([toolbar.items[1].itemIdentifier isEqual:@"back"]) { + if (!show) { + [toolbar removeItemAtIndex:1]; + } + } + else if ([toolbar.items[0].itemIdentifier isEqual:@"back"]) { + if (!show) { + [toolbar removeItemAtIndex:0]; + } + } + else if (show) { + // Insert it after the sidebar toggle, if we have one. + int index = ([toolbar.items[0].itemIdentifier isEqual:NSToolbarToggleSidebarItemIdentifier]); + [toolbar insertItemWithItemIdentifier:@"back" atIndex:index]; + } +} + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { + return @[]; +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return @[]; +} + +- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar + itemForItemIdentifier:(NSString *)ident + willBeInsertedIntoToolbar:(BOOL)flag { + + if (@available(macOS 11.0, *)) { + if ([ident isEqual:@"sep"]) { + return [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:@"sep" splitView:_graph->get_split_view() dividerIndex:0]; + } + + if ([ident isEqual:@"back"]) { + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease]; + item.label = @"Back"; + item.image = [NSImage imageWithSystemSymbolName:@"chevron.left" accessibilityDescription:@""]; + item.target = self; + item.action = @selector(handleBack:); + [item setNavigational:YES]; + [item setBordered:YES]; + return item; + } + } + + return nil; +} + +- (void)handleSetAsFocus:(NSMenuItem *)item { + _graph->on_click_label(item.tag); +} + +- (void)handleChangeColor:(NSMenuItem *)item { + _graph->get_monitor()->choose_collector_color(item.tag); +} + +- (void)handleResetColor:(NSMenuItem *)item { + _graph->get_monitor()->reset_collector_color(item.tag); +} + +- (void)handleBack:(id)sender { + _graph->handle_back(); +} + +@end + +@implementation MacStatsScrollableGraphViewController + +- (void)loadView { + NSView *graph_view = [[MacStatsGraphView alloc] initWithGraph:_graph]; + NSScrollView *scroll = [[NSScrollView alloc] init]; + scroll.hasHorizontalScroller = NO; + scroll.hasVerticalScroller = YES; + scroll.horizontalScrollElasticity = NSScrollElasticityNone; + scroll.usesPredominantAxisScrolling = NO; + scroll.drawsBackground = YES; + scroll.scrollerStyle = NSScrollerStyleOverlay; + scroll.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + //scroll.translatesAutoresizingMaskIntoConstraints = NO; + scroll.automaticallyAdjustsContentInsets = YES; + scroll.documentView = graph_view; + self.view = scroll; + + [graph_view.widthAnchor constraintEqualToAnchor:scroll.widthAnchor].active = YES; + + if (@available(macOS 11.0, *)) { + [graph_view.heightAnchor constraintGreaterThanOrEqualToAnchor:((NSLayoutGuide *)[scroll safeAreaLayoutGuide]).heightAnchor].active = YES; + } else { + [graph_view.heightAnchor constraintGreaterThanOrEqualToAnchor:scroll.heightAnchor].active = YES; + } + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(handleScroll:) + name:NSScrollViewDidLiveScrollNotification + object:scroll]; + [scroll release]; + [graph_view release]; +} + +- (MacStatsGraphView *)graphView { + return (MacStatsGraphView *)((NSScrollView *)self.view).documentView; +} + +- (NSClipView *)clipView { + return ((NSScrollView *)self.view).contentView; +} + +- (void)viewDidLayout { + if (_graph != nullptr) { + _graph->handle_scroll(); + } +} + +- (void)handleScroll:(NSNotification *)notification { + if (_graph != nullptr) { + _graph->handle_scroll(); + } +} + +- (void)handleSideScroll:(NSNotification *)notification { + // Graph view is flipped, side bar isn't, so we need to convert coordinates + NSScrollView *side_sv = ((NSScrollView *)notification.object); + NSScrollView *graph_sv = (NSScrollView *)self.view; + NSPoint point; + point.x = 0; + point.y = self.graphView.frame.size.height - (side_sv.documentVisibleRect.size.height + side_sv.documentVisibleRect.origin.y) - graph_sv.contentInsets.top; + [graph_sv.contentView scrollToPoint:point]; + [graph_sv reflectScrolledClipView:graph_sv.contentView]; +} + +@end diff --git a/pandatool/src/mac-stats/macStatsLabel.h b/pandatool/src/mac-stats/macStatsLabel.h new file mode 100644 index 00000000..1171ba25 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsLabel.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsLabel.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSLABEL_H +#define MACSTATSLABEL_H + +#include "pandatoolbase.h" +#include "luse.h" + +#include "cocoa_compat.h" + +class MacStatsMonitor; +class MacStatsGraph; + +/** + * A text label that will draw in color appropriate for a particular + * collector. It also responds when the user double-clicks on it. This is + * handy for putting colored labels on strip charts. + */ +@interface MacStatsLabel : NSTextField { + @private + MacStatsGraph *_graph; + int _thread_index; + int _collector_index; + bool _highlight; + bool _mouse_within; + NSColor *_fg_color; + NSColor *_highlight_fg_color; + NSColor *_bg_color; + NSColor *_highlight_bg_color; +} + +- (id)initWithText:(NSString *)text + graph:(MacStatsGraph *)graph + threadIndex:(int)thread_index + collectorIndex:(int)collector_index; + +- (int)threadIndex; +- (int)collectorIndex; + +- (void)updateColor; + +- (BOOL)highlight; +- (void)setHighlight:(BOOL)highlight; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsLabel.mm b/pandatool/src/mac-stats/macStatsLabel.mm new file mode 100644 index 00000000..3169c58f --- /dev/null +++ b/pandatool/src/mac-stats/macStatsLabel.mm @@ -0,0 +1,137 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsLabel.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsLabel.h" +#include "macStatsMonitor.h" +#include "macStatsGraph.h" + +@implementation MacStatsLabel + +- (id)initWithText:(NSString *)text + graph:(MacStatsGraph *)graph + threadIndex:(int)thread_index + collectorIndex:(int)collector_index { + if (self = [super init]) { + _graph = graph; + _thread_index = thread_index; + _collector_index = collector_index; + _bg_color = nil; + _highlight_bg_color = nil; + + [self setStringValue:text]; + self.bezeled = NO; + self.drawsBackground = YES; + self.selectable = NO; + self.editable = NO; + self.lineBreakMode = NSLineBreakByTruncatingTail; + //self.autoresizingMask = NSViewWidthSizable | NSViewMaxXMargin | NSViewMinXMargin; + //self.translatesAutoresizingMaskIntoConstraints = NO; + + NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil]; + [self addTrackingArea:area]; + [area release]; + + [self addToolTipRect:NSMakeRect(0, 0, 1000, 1000) owner:self userData:nil]; + + [self updateColor]; + } + + return self; +} + +- (void)dealloc { + [_bg_color release]; + [_highlight_bg_color release]; + [super dealloc]; +} + +- (NSSize)intrinsicContentSize { + // Allow resizing down. + NSSize size = [super intrinsicContentSize]; + return NSMakeSize(NSViewNoIntrinsicMetric, size.height); +} + +- (int)threadIndex { + return _thread_index; +} + +- (int)collectorIndex { + return _collector_index; +} + +- (void)updateColor { + if (_bg_color != nil) { + [_bg_color release]; + } + if (_highlight_bg_color != nil) { + [_highlight_bg_color release]; + } + + _bg_color = [NSColor colorWithCGColor:_graph->get_monitor()->get_collector_color(_collector_index, false)]; + _highlight_bg_color = [NSColor colorWithCGColor:_graph->get_monitor()->get_collector_color(_collector_index, true)]; + + [_bg_color retain]; + [_highlight_bg_color retain]; + + _fg_color = _graph->get_monitor()->get_collector_text_color(_collector_index, false); + _highlight_fg_color = _graph->get_monitor()->get_collector_text_color(_collector_index, true); + + [self setHighlight:_highlight]; +} + +- (BOOL)highlight { + return _highlight; +} + +- (void)setHighlight:(BOOL)highlight { + _highlight = highlight; + if (highlight || _mouse_within) { + self.backgroundColor = _highlight_bg_color; + self.textColor = _highlight_fg_color; + } else { + self.backgroundColor = _bg_color; + self.textColor = _fg_color; + } +} + +- (void)mouseEntered:(NSEvent *)event { + _mouse_within = true; + self.backgroundColor = _highlight_bg_color; + self.textColor = _highlight_fg_color; +} + +- (void)mouseExited:(NSEvent *)event { + _mouse_within = false; + [self setHighlight:_highlight]; +} + +- (void)mouseDown:(NSEvent *)event { + if (event.buttonNumber == 0 && event.clickCount == 2) { + _graph->on_click_label(_collector_index); + } +} + +- (NSMenu *)menuForEvent:(NSEvent *)event { + return _graph->get_label_menu(_collector_index); +} + +- (NSString *)view:(NSView *)view + stringForToolTip:(NSToolTipTag)tag + point:(NSPoint)point + userData:(void *)data { + + std::string text = _graph->get_label_tooltip(_collector_index); + return [NSString stringWithUTF8String:text.c_str()]; +} + +@end diff --git a/pandatool/src/mac-stats/macStatsLabelStack.h b/pandatool/src/mac-stats/macStatsLabelStack.h new file mode 100644 index 00000000..9aabb3b2 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsLabelStack.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsLabelStack.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATSLABELSTACK_H +#define MACSTATSLABELSTACK_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "macStatsLabel.h" + +#include + +class MacStatsMonitor; +class MacStatsGraph; + +/** + * A widget that contains a stack of labels from bottom to top. + */ +class MacStatsLabelStack { +public: + MacStatsLabelStack(); + ~MacStatsLabelStack(); + + NSView *get_view() const; + + int get_label_y(int label_index, NSView *target_view) const; + int get_label_height(int label_index) const; + int get_label_collector_index(int label_index) const; + + void clear_labels(); + int add_label(MacStatsMonitor *monitor, MacStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname); + int get_num_labels() const; + + void highlight_label(int collector_index); + void update_label_color(int collector_index); + +private: + NSStackView *_stack_view; + NSLayoutConstraint *_constraint; + int _highlight_label; + + typedef pvector Labels; + Labels _labels; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsLabelStack.mm b/pandatool/src/mac-stats/macStatsLabelStack.mm new file mode 100644 index 00000000..6bfa9c3f --- /dev/null +++ b/pandatool/src/mac-stats/macStatsLabelStack.mm @@ -0,0 +1,174 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsLabelStack.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "macStatsLabelStack.h" +#include "macStatsLabel.h" +#include "macStatsMonitor.h" + +static const NSEdgeInsets insets = {8, 8, 8, 8}; + +/** + * + */ +MacStatsLabelStack:: +MacStatsLabelStack() { + _stack_view = [[NSStackView alloc] init]; + _stack_view.edgeInsets = insets; + _stack_view.orientation = NSUserInterfaceLayoutOrientationVertical; + _stack_view.alignment = NSLayoutAttributeRight;//NSLayoutAttributeCenterX; + _stack_view.distribution = NSStackViewDistributionGravityAreas; + _stack_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _stack_view.translatesAutoresizingMaskIntoConstraints = NO; + _stack_view.spacing = 0; + _highlight_label = -1; + + // May never be wider than the widest label, or 68, whichever is larger + //FIXME + _constraint = [_stack_view.widthAnchor constraintLessThanOrEqualToConstant:68]; + _constraint.active = NO; + [_constraint retain]; +} + +/** + * + */ +MacStatsLabelStack:: +~MacStatsLabelStack() { + clear_labels(); + [_constraint release]; + [_stack_view release]; +} + +/** + * Returns the view for this stack. + */ +NSView *MacStatsLabelStack:: +get_view() const { + return _stack_view; +} + +/** + * Returns the y position of the indicated label's bottom edge, relative to + * the indicated target widget. + */ +int MacStatsLabelStack:: +get_label_y(int label_index, NSView *target_view) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + + MacStatsLabel *label = _labels[label_index]; + NSPoint pos = [target_view convertPoint:NSMakePoint(0, label.frame.size.height) fromView:label]; + return pos.y; +} + +/** + * Returns the height of the indicated label. + */ +int MacStatsLabelStack:: +get_label_height(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + return _labels[label_index].frame.size.height; +} + +/** + * Returns the collector index associated with the indicated label. + */ +int MacStatsLabelStack:: +get_label_collector_index(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), -1); + return _labels[label_index].collectorIndex; +} + +/** + * Removes the set of labels and starts a new set. + */ +void MacStatsLabelStack:: +clear_labels() { + for (MacStatsLabel *label : _labels) { + [_stack_view removeView:label]; + [label release]; + } + _labels.clear(); + + _constraint.constant = 68; + + NSRect frame = _stack_view.frame; + frame.size.width = 0; + _stack_view.frame = frame; +} + +/** + * Adds a new label to the top of the stack; returns the new label index. + */ +int MacStatsLabelStack:: +add_label(MacStatsMonitor *monitor, MacStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname) { + const PStatClientData *client_data = monitor->get_client_data(); + std::string text; + if (use_fullname) { + text = client_data->get_collector_fullname(collector_index); + } else { + text = client_data->get_collector_name(collector_index); + } + + MacStatsLabel *label = [MacStatsLabel alloc]; + [label initWithText:[NSString stringWithUTF8String:text.c_str()] + graph:graph + threadIndex:thread_index + collectorIndex:collector_index]; + + [_stack_view insertView:label atIndex:0 inGravity:NSStackViewGravityBottom]; + [label.leadingAnchor constraintEqualToAnchor:_stack_view.leadingAnchor constant:8].active = YES; + [label.trailingAnchor constraintLessThanOrEqualToAnchor:_stack_view.trailingAnchor constant:-8].active = YES; + + _constraint.constant = std::max(_constraint.constant, label.intrinsicContentSize.width); + + int label_index = (int)_labels.size(); + _labels.push_back(label); + + return label_index; +} + +/** + * Returns the number of labels in the stack. + */ +int MacStatsLabelStack:: +get_num_labels() const { + return _labels.size(); +} + +/** + * Draws a highlight around the label representing the indicated collector, + * and removes the highlight from any other label. Specify -1 to remove the + * highlight from all labels. + */ +void MacStatsLabelStack:: +highlight_label(int collector_index) { + if (_highlight_label != collector_index) { + _highlight_label = collector_index; + for (MacStatsLabel *label : _labels) { + label.highlight = (label.collectorIndex == _highlight_label); + } + } +} + +/** + * Refreshes the color of the label with the given index. + */ +void MacStatsLabelStack:: +update_label_color(int collector_index) { + for (MacStatsLabel *label : _labels) { + if (label.collectorIndex == collector_index) { + [label updateColor]; + } + } +} diff --git a/pandatool/src/mac-stats/macStatsMonitor.h b/pandatool/src/mac-stats/macStatsMonitor.h new file mode 100644 index 00000000..8c5d0c31 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsMonitor.h @@ -0,0 +1,117 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsMonitor.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATSMONITOR_H +#define MACSTATSMONITOR_H + +#include "pandatoolbase.h" + +#include "pStatMonitor.h" +#include "pointerTo.h" +#include "pset.h" +#include "pvector.h" +#include "pmap.h" + +#import + +class MacStatsGraph; +class MacStatsServer; +class MacStatsChartMenu; + +/** + * This class represents a connection to a PStatsClient and manages the data + * exchange with the client. + */ +class MacStatsMonitor : public PStatMonitor { +public: + MacStatsMonitor(MacStatsServer *server); + virtual ~MacStatsMonitor(); + + void close(); + + virtual std::string get_monitor_name(); + + virtual void initialized(); + virtual void got_hello(); + virtual void got_bad_version(int client_major, int client_minor, + int server_major, int server_minor); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + virtual void remove_thread(int thread_index); + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + + virtual void user_guide_bars_changed(); + + PStatGraph *open_timeline(); + PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + PStatGraph *open_flame_graph(int thread_index, int collector_index = -1, int frame_number = -1); + PStatGraph *open_piano_roll(int thread_index); + + CGColorRef get_collector_color(int collector_index, bool highlight = false); + NSColor *get_collector_text_color(int collector_index, bool highlight = false); + + void choose_collector_color(int collector_index); + void handle_choose_collector_color(const LRGBColor &color); + void reset_collector_color(int collector_index); + + void set_show_status_item(bool show); + void set_time_units(int unit_mask); + void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void add_graph(MacStatsGraph *graph); + void remove_graph(MacStatsGraph *graph); + void close_all_graphs(); + +private: + void setup_speed_menu(); + void update_status_bar(); + +private: + typedef pset Graphs; + Graphs _graphs; + + typedef pvector ChartMenus; + ChartMenus _chart_menus; + + NSUserNotification *_notification = nullptr; + NSMenu *_main_menu; + NSMenuItem *_speed_menu_item = nullptr; + NSMenuItem *_speed_menu_item_1; + NSMenuItem *_speed_menu_item_2; + NSMenuItem *_speed_menu_item_3; + NSMenuItem *_speed_menu_item_6; + NSMenuItem *_speed_menu_item_12; + NSMenuItem *_speed_menu_item_pause; + int _next_chart_index; + NSStatusItem *_frame_rate_status_item = nullptr; + double _scroll_speed; + bool _pause; + bool _have_data = false; + int _choosing_color_collector_index; + + struct ColorSet { + CGColorRef _bg[2]; + NSColor *_fg[2]; + }; + typedef pmap Colors; + Colors _colors; + + friend class MacStatsGraph; + friend class MacStatsServer; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsMonitor.mm b/pandatool/src/mac-stats/macStatsMonitor.mm new file mode 100644 index 00000000..57245e02 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsMonitor.mm @@ -0,0 +1,647 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsMonitor.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "macStatsMonitor.h" +#include "macStats.h" +#include "macStatsServer.h" +#include "macStatsStripChart.h" +#include "macStatsChartMenu.h" +#include "macStatsPianoRoll.h" +#include "macStatsFlameGraph.h" +#include "macStatsTimeline.h" +#include "pStatGraph.h" +#include "pStatCollectorDef.h" + +#include "convert_srgb.h" + +/** + * + */ +MacStatsMonitor:: +MacStatsMonitor(MacStatsServer *server) : PStatMonitor(server) { + _main_menu = NSApp.mainMenu; + + // These will be filled in later when the menu is created. + _scroll_speed = 0.0; + _pause = false; + _next_chart_index = 2; + + setup_speed_menu(); + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowStatusItem"]) { + set_show_status_item(true); + } +} + +/** + * + */ +MacStatsMonitor:: +~MacStatsMonitor() { + close(); +} + +/** + * Closes all the graphs associated with this monitor. + */ +void MacStatsMonitor:: +close() { + PStatMonitor::close(); + + if (_notification != nil) { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + [center removeDeliveredNotification:_notification]; + [_notification release]; + _notification = nil; + } + + close_all_graphs(); + + if (_speed_menu_item != nullptr) { + [_main_menu removeItem:_speed_menu_item]; + [_speed_menu_item release]; + _speed_menu_item = nullptr; + } + + for (MacStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->remove_from_menu(_main_menu); + delete chart_menu; + } + _chart_menus.clear(); + + set_show_status_item(false); + + for (auto &item : _colors) { + CGColorRelease(item.second._bg[0]); + CGColorRelease(item.second._bg[1]); + } + _colors.clear(); + + _next_chart_index = 2; +} + +/** + * Should be redefined to return a descriptive name for the type of + * PStatsMonitor this is. + */ +std::string MacStatsMonitor:: +get_monitor_name() { + return "MacStats"; +} + +/** + * Called after the monitor has been fully set up. At this time, it will have + * a valid _client_data pointer, and things like is_alive() and close() will + * be meaningful. However, we may not yet know who we're connected to + * (is_client_known() may return false), and we may not know anything about + * the threads or collectors we're about to get data on. + */ +void MacStatsMonitor:: +initialized() { +} + +/** + * Called when the "hello" message has been received from the client. At this + * time, the client's hostname and program name will be known. + */ +void MacStatsMonitor:: +got_hello() { + std::string progname = get_client_progname(); + std::string hostname = get_client_hostname(); + + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + _notification = [[NSUserNotification alloc] init]; + _notification.title = @"PStats Server"; + _notification.informativeText = [NSString + stringWithFormat:@"Connected to %s on %s", progname.c_str(), hostname.c_str()]; + + [center deliverNotification:_notification]; +} + +/** + * Like got_hello(), this is called when the "hello" message has been received + * from the client. At this time, the client's hostname and program name will + * be known. However, the client appears to be an incompatible version and + * the connection will be terminated; the monitor should issue a message to + * that effect. + */ +void MacStatsMonitor:: +got_bad_version(int client_major, int client_minor, + int server_major, int server_minor) { + std::ostringstream str; + str << "Unable to honor connection attempt from " + << get_client_progname() << " on " << get_client_hostname() + << ": unsupported PStats version " + << client_major << "." << client_minor; + + if (server_minor == 0) { + str << " (server understands version " << server_major + << "." << server_minor << " only)."; + } else { + str << " (server understands versions " << server_major + << ".0 through " << server_major << "." << server_minor << ")."; + } + + std::string message = str.str(); + + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithUTF8String:message.c_str()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; +} + +/** + * Called whenever a new Collector definition is received from the client. + * Generally, the client will send all of its collectors over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Collector definitions midstream. + */ +void MacStatsMonitor:: +new_collector(int collector_index) { + for (MacStatsGraph *graph : _graphs) { + graph->new_collector(collector_index); + } +} + +/** + * Called whenever a new Thread definition is received from the client. + * Generally, the client will send all of its threads over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Thread definitions midstream. + */ +void MacStatsMonitor:: +new_thread(int thread_index) { + MacStatsChartMenu *chart_menu = new MacStatsChartMenu(this, thread_index); + chart_menu->add_to_menu(_main_menu, _next_chart_index); + ++_next_chart_index; + _chart_menus.push_back(chart_menu); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void MacStatsMonitor:: +new_data(int thread_index, int frame_number) { + PStatMonitor::new_data(thread_index, frame_number); + + for (MacStatsGraph *graph : _graphs) { + graph->new_data(thread_index, frame_number); + } + + if (!_have_data) { + open_default_graphs(); + _have_data = true; + } +} + +/** + * Called when a thread should be removed from the list of threads. + */ +void MacStatsMonitor:: +remove_thread(int thread_index) { + for (ChartMenus::iterator it = _chart_menus.begin(); it != _chart_menus.end(); ++it) { + MacStatsChartMenu *chart_menu = *it; + if (chart_menu->get_thread_index() == thread_index) { + chart_menu->remove_from_menu(_main_menu); + delete chart_menu; + _chart_menus.erase(it); + --_next_chart_index; + return; + } + } +} + +/** + * Called whenever the connection to the client has been lost. This is a + * permanent state change. The monitor should update its display to represent + * this, and may choose to close down automatically. + */ +void MacStatsMonitor:: +lost_connection() { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + + if (_notification != nil) { + [center removeDeliveredNotification:_notification]; + [_notification release]; + } + + std::string hostname = get_client_hostname(); + + _notification = [[NSUserNotification alloc] init]; + _notification.title = @"PStats Server"; + _notification.informativeText = [NSString + stringWithFormat:@"Lost connection to %s", hostname.c_str()]; + + _notification.additionalActions = @[ + [NSUserNotificationAction actionWithIdentifier:@"new" title:@"New Session"], + [NSUserNotificationAction actionWithIdentifier:@"quit" title:@"Quit PStats"] + ]; + + [center deliverNotification:_notification]; +} + +/** + * If has_idle() returns true, this will be called periodically to allow the + * monitor to update its display or whatever it needs to do. + */ +void MacStatsMonitor:: +idle() { + // Check if any of our chart menus need updating. + for (MacStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->check_update(); + } + + // Update the frame rate label from the main thread (thread 0). + if (_frame_rate_status_item != nil) { + const PStatThreadData *thread_data = get_client_data()->get_thread_data(0); + double frame_rate = thread_data->get_frame_rate(); + if (frame_rate != 0.0f) { + _frame_rate_status_item.button.title = + [NSString stringWithFormat:@"%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate]; + } + } +} + +/** + * Should be redefined to return true if you want to redefine idle() and + * expect it to be called. + */ +bool MacStatsMonitor:: +has_idle() { + return true; +} + +/** + * Called when the user guide bars have been changed. + */ +void MacStatsMonitor:: +user_guide_bars_changed() { + for (MacStatsGraph *graph : _graphs) { + graph->user_guide_bars_changed(); + } +} + +/** + * Opens a new timeline. + */ +PStatGraph *MacStatsMonitor:: +open_timeline() { + MacStatsTimeline *graph = new MacStatsTimeline(this); + add_graph(graph); + return graph; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *MacStatsMonitor:: +open_flame_graph(int thread_index, int collector_index, int frame_number) { + MacStatsFlameGraph *graph = + new MacStatsFlameGraph(this, thread_index, collector_index, frame_number); + add_graph(graph); + return graph; +} + +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *MacStatsMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + MacStatsStripChart *graph = + new MacStatsStripChart(this, thread_index, collector_index, show_level); + add_graph(graph); + return graph; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *MacStatsMonitor:: +open_piano_roll(int thread_index) { + MacStatsPianoRoll *graph = new MacStatsPianoRoll(this, thread_index); + add_graph(graph); + return graph; +} + +/** + * Returns a color suitable for drawing the indicated collector. + */ +CGColorRef MacStatsMonitor:: +get_collector_color(int collector_index, bool highlight) { + Colors::iterator bi; + bi = _colors.find(collector_index); + if (bi != _colors.end()) { + return (*bi).second._bg[highlight]; + } + + LRGBColor rgb = PStatMonitor::get_collector_color(collector_index); + rgb[0] = encode_sRGB_float((float)rgb[0]); + rgb[1] = encode_sRGB_float((float)rgb[1]); + rgb[2] = encode_sRGB_float((float)rgb[2]); + + PN_stdfloat bright = rgb.dot(LRGBColor(0.2126, 0.7152, 0.0722)); + CGColorRef color = CGColorCreateGenericRGB(rgb[0], rgb[1], rgb[2], 1.0); + + rgb *= 0.75; + CGColorRef hcolor = CGColorCreateGenericRGB(rgb[0], rgb[1], rgb[2], 1.0); + + ColorSet &set = _colors[collector_index]; + set._bg[0] = color; + set._bg[1] = hcolor; + + if (bright >= 0.5) { + set._fg[0] = [NSColor blackColor]; + } else { + set._fg[0] = [NSColor whiteColor]; + } + + if (bright * 0.75 >= 0.5) { + set._fg[1] = [NSColor blackColor]; + } else { + set._fg[1] = [NSColor whiteColor]; + } + + return highlight ? hcolor : color; +} + +/** + * Returns a color suitable for drawing text on the indicated collector. + */ +NSColor *MacStatsMonitor:: +get_collector_text_color(int collector_index, bool highlight) { + get_collector_color(collector_index, false); + return _colors[collector_index]._fg[highlight]; +} + +/** + * Opens a dialog to change the given collector color. + */ +void MacStatsMonitor:: +choose_collector_color(int collector_index) { + _choosing_color_collector_index = collector_index; + + const LRGBColor &rgb = PStatMonitor::get_collector_color(collector_index); + NSColor *color = [NSColor colorWithSRGBRed:rgb[0] green:rgb[1] blue:rgb[2] alpha:1.0]; + + NSColorPanel *panel = [NSColorPanel sharedColorPanel]; + panel.target = [NSApp delegate]; + panel.action = @selector(handleChooseCollectorColor:); + panel.showsAlpha = NO; + panel.color = color; + [NSApp orderFrontColorPanel:panel]; +} + +/** + * Sets a custom color associated with the given collector. + */ +void MacStatsMonitor:: +handle_choose_collector_color(const LRGBColor &color) { + int collector_index = _choosing_color_collector_index; + + PStatMonitor::set_collector_color(collector_index, color); + + if (_colors.erase(collector_index)) { + for (MacStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } +} + +/** + * Resets the color of the given collector to the default. + */ +void MacStatsMonitor:: +reset_collector_color(int collector_index) { + PStatMonitor::clear_collector_color(collector_index); + + if (_colors.erase(collector_index)) { + for (MacStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } +} + +/** + * Enables the frame rate label on the right end of the menu bar. This is + * used as a text label to display the main thread's frame rate to the user, + * although it is implemented as a right-justified toplevel menu item that + * doesn't open to anything. + */ +void MacStatsMonitor:: +set_show_status_item(bool show) { + if (show && _frame_rate_status_item == nil) { + _frame_rate_status_item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + _frame_rate_status_item.button.action = @selector(handleClickStatusItem:); + [_frame_rate_status_item retain]; + + const PStatClientData *client_data = get_client_data(); + if (client_data != nullptr) { + const PStatThreadData *thread_data = client_data->get_thread_data(0); + if (thread_data != nullptr) { + double frame_rate = thread_data->get_frame_rate(); + if (frame_rate != 0.0f) { + _frame_rate_status_item.button.title = + [NSString stringWithFormat:@"%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate]; + } + } + } + } + if (!show && _frame_rate_status_item != nil) { + // Careful: for some reason, this can get called recursively. + NSStatusItem *status_item = _frame_rate_status_item; + _frame_rate_status_item = nil; + [[NSStatusBar systemStatusBar] removeStatusItem:status_item]; + [status_item release]; + status_item = nil; + } +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void MacStatsMonitor:: +set_time_units(int unit_mask) { + for (MacStatsGraph *graph : _graphs) { + graph->set_time_units(unit_mask); + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speeds for all graphs to the indicated value. + */ +void MacStatsMonitor:: +set_scroll_speed(double scroll_speed) { + _scroll_speed = scroll_speed; + + // First, change all of the open graphs appropriately. + for (MacStatsGraph *graph : _graphs) { + graph->set_scroll_speed(_scroll_speed); + } + + // Then, change the state of the menu items. + _speed_menu_item_1.state = (scroll_speed == 1.0); + _speed_menu_item_2.state = (scroll_speed == 2.0); + _speed_menu_item_3.state = (scroll_speed == 3.0); + _speed_menu_item_6.state = (scroll_speed == 6.0); + _speed_menu_item_12.state = (scroll_speed == 12.0); +} + +/** + * Called when the user selects a pause on or pause off option from the menu. + */ +void MacStatsMonitor:: +set_pause(bool pause) { + _pause = pause; + + // First, change all of the open graphs appropriately. + for (MacStatsGraph *graph : _graphs) { + graph->set_pause(_pause); + } + + // Then, change the state of the menu item. + _speed_menu_item_pause.state = pause; +} + +/** + * Adds the newly-created graph to the list of managed graphs. + */ +void MacStatsMonitor:: +add_graph(MacStatsGraph *graph) { + _graphs.insert(graph); + + int units = [[NSUserDefaults standardUserDefaults] integerForKey:@"TimeUnits"]; + graph->set_time_units(units); + graph->set_scroll_speed(_scroll_speed); + graph->set_pause(_pause); +} + +/** + * Deletes the indicated graph. + */ +void MacStatsMonitor:: +remove_graph(MacStatsGraph *graph) { + Graphs::iterator gi = _graphs.find(graph); + if (gi != _graphs.end()) { + _graphs.erase(gi); + delete graph; + } +} + +/** + * Asks all open graphs to close. + */ +void MacStatsMonitor:: +close_all_graphs() { + while (!_graphs.empty()) { + (*_graphs.begin())->close(); + } +} + +/** + * Creates the "Speed" pulldown menu. + */ +void MacStatsMonitor:: +setup_speed_menu() { + NSMenu *menu = [[NSMenu alloc] init]; + menu.title = @"Speed"; + menu.autoenablesItems = NO; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSpeed:); + item.title = @"1"; + item.tag = 1; + [menu addItem:item]; + [item release]; + + _speed_menu_item_1 = item; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSpeed:); + item.title = @"2"; + item.tag = 2; + [menu addItem:item]; + [item release]; + + _speed_menu_item_2 = item; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSpeed:); + item.title = @"3"; + item.tag = 3; + item.state = NSOnState; + [menu addItem:item]; + [item release]; + + _speed_menu_item_3 = item; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSpeed:); + item.title = @"6"; + item.tag = 6; + [menu addItem:item]; + [item release]; + + _speed_menu_item_6 = item; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSpeed:); + item.title = @"12"; + item.tag = 12; + [menu addItem:item]; + [item release]; + + _speed_menu_item_12 = item; + } + + [menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handlePause:); + item.title = @"pause"; + [menu addItem:item]; + [item release]; + + _speed_menu_item_pause = item; + } + + NSMenuItem *item = [[NSMenuItem alloc] init]; + _speed_menu_item = item; + [_main_menu addItem:item]; + [_main_menu setSubmenu:menu forItem:item]; + [menu release]; + + set_scroll_speed(3); + set_pause(false); + + ++_next_chart_index; +} diff --git a/pandatool/src/mac-stats/macStatsPianoRoll.h b/pandatool/src/mac-stats/macStatsPianoRoll.h new file mode 100644 index 00000000..df156619 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsPianoRoll.h @@ -0,0 +1,84 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsPianoRoll.h + * @author rdb + * @date 2023-08-19 + */ + +#ifndef MACSTATSPIANOROLL_H +#define MACSTATSPIANOROLL_H + +#include "macStatsGraph.h" +#include "pStatPianoRoll.h" +#include "macStatsChartMenuDelegate.h" + +class MacStatsMonitor; + +/** + * A window that draws a piano-roll style chart, which shows the collectors + * explicitly stopping and starting, one frame at a time. + */ +class MacStatsPianoRoll final : public PStatPianoRoll, public MacStatsGraph { +public: + MacStatsPianoRoll(MacStatsMonitor *monitor, int thread_index); + virtual ~MacStatsPianoRoll(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual NSMenu *get_label_menu(int collector_index) const; + virtual std::string get_label_tooltip(int collector_index) const; + void set_horizontal_scale(double time_width); + +protected: + void clear_region(); + virtual void begin_draw(); + virtual void begin_row(int row); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_draw(); + virtual void idle(); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const; + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual void handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual void handle_button_release(int graph_x, int graph_y); + virtual void handle_motion(int graph_x, int graph_y); + virtual void handle_leave(); + virtual void handle_scroll(); + virtual void handle_magnify(int graph_x, int graph_y, double scale); + virtual void handle_draw_graph(CGContextRef ctx, NSRect rect); + virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect); + virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect); + +private: + int get_collector_under_pixel(int xpoint, int ypoint) const; + void update_labels(); + void draw_guide_bars(CGContextRef ctx, int y, int height); + void draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar, + int y, int height); + void draw_guide_labels(CGContextRef ctx); + void draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar); + +private: + MacStatsChartMenuDelegate *_menu_delegate; + NSScrollView *_sidebar_scroll_view; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsPianoRoll.mm b/pandatool/src/mac-stats/macStatsPianoRoll.mm new file mode 100644 index 00000000..fe620b7f --- /dev/null +++ b/pandatool/src/mac-stats/macStatsPianoRoll.mm @@ -0,0 +1,764 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsPianoRoll.mm + * @author rdb + * @date 2023-08-19 + */ + +#include "macStatsPianoRoll.h" +#include "macStatsMonitor.h" +#include "macStatsLabelStack.h" +#include "macStatsScaleArea.h" + +static const int default_piano_roll_width = 800; +static const int default_piano_roll_height = 400; + +static const int minimum_piano_roll_sidebar_width = 68; +static const int default_piano_roll_sidebar_width = 200; + +/** + * + */ +MacStatsPianoRoll:: +MacStatsPianoRoll(MacStatsMonitor *monitor, int thread_index) : + PStatPianoRoll(monitor, thread_index, 0, 0), + MacStatsGraph(monitor, [MacStatsScrollableGraphViewController alloc]) +{ + // Used for popup menus. + _menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index]; + + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + const PStatClientData *client_data = + MacStatsGraph::_monitor->get_client_data(); + std::string thread_name = client_data->get_thread_name(_thread_index); + std::string window_title = thread_name + " thread piano roll"; + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + + if (@available(macOS 11.0, *)) { + _window.titleVisibility = NSWindowTitleHidden; + } + + // Set the initial size of the graph. + _graph_view.frame = NSMakeRect(0, 0, default_piano_roll_width, default_piano_roll_height); + _graph_view_controller.view.frame = NSMakeRect(0, 0, default_piano_roll_width, default_piano_roll_height); + + // It's put inside a scroll view that tracks the main scroll view. + NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_piano_roll_sidebar_width, 0)]; + scroll_view.documentView = _label_stack.get_view(); + scroll_view.drawsBackground = NO; + scroll_view.automaticallyAdjustsContentInsets = YES; + //scroll_view.translatesAutoresizingMaskIntoConstraints = NO; + scroll_view.hasHorizontalScroller = NO; + scroll_view.hasVerticalScroller = NO; + _sidebar_scroll_view = scroll_view; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:_graph_view_controller + selector:@selector(handleSideScroll:) + name:NSScrollViewDidLiveScrollNotification + object:scroll_view]; + + NSViewController *sidebar_controller = [[NSViewController alloc] init]; + sidebar_controller.view = scroll_view; + + NSSplitViewController *svc = [[NSSplitViewController alloc] init]; + [svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]]; + [svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]]; + + svc.splitViewItems[0].minimumThickness = minimum_piano_roll_sidebar_width; + svc.splitViewItems[0].canCollapse = NO; + + NSSplitView *split_view = svc.splitView; + split_view.vertical = YES; + split_view.dividerStyle = NSSplitViewDividerStyleThin; + split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _split_view = split_view; + + _window.contentViewController = svc; + + [svc release]; + [sidebar_controller release]; + + // Scale area goes on top, as a titlebar accessory view. + MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this]; + scale_area_controller.fullScreenMinHeight = 20; + scale_area_controller.layoutAttribute = NSLayoutAttributeRight; + _scale_area = scale_area_controller.view; + [_window addTitlebarAccessoryViewController:scale_area_controller]; + [scale_area_controller release]; + + [_label_stack.get_view().widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES; + [_label_stack.get_view().heightAnchor constraintEqualToAnchor:_graph_view.heightAnchor].active = YES; + + idle(); + + [_window makeKeyAndOrderFront:nil]; +} + +/** + * + */ +MacStatsPianoRoll:: +~MacStatsPianoRoll() { + [_sidebar_scroll_view release]; + [_menu_delegate release]; +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void MacStatsPianoRoll:: +new_data(int thread_index, int frame_number) { + if (!_pause) { + update(); + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void MacStatsPianoRoll:: +force_redraw() { + if (_ctx) { + PStatPianoRoll::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void MacStatsPianoRoll:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatPianoRoll::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void MacStatsPianoRoll:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void MacStatsPianoRoll:: +on_click_label(int collector_index) { + if (collector_index >= 0) { + MacStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false); + } +} + +/** + * Called when the mouse right-clicks on a label, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsPianoRoll:: +get_label_menu(int collector_index) const { + NSMenu *menu = [[[NSMenu alloc] init] autorelease]; + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + if (@available(macOS 14.0, *)) { + [menu addItem:[NSMenuItem sectionHeaderWithTitle:[NSString stringWithUTF8String:label.c_str()]]]; + } else { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label.c_str()] action:nil keyEquivalent:@""]; + item.enabled = NO; + [menu addItem:item]; + [item release]; + } + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:@selector(handleOpenStripChart:) keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Change Color\u2026" action:@selector(handleChangeColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Reset Color" action:@selector(handleResetColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + return menu; +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsPianoRoll:: +get_label_tooltip(int collector_index) const { + return PStatPianoRoll::get_label_tooltip(collector_index); +} + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +void MacStatsPianoRoll:: +set_horizontal_scale(double time_width) { + PStatPianoRoll::set_horizontal_scale(time_width); + + _graph_view.needsDisplay = YES; +} + +/** + * Erases the chart area. + */ +void MacStatsPianoRoll:: +clear_region() { + if (_ctx) { + CGContextSetFillColorWithColor(_ctx, _background_color); + CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize())); + } +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void MacStatsPianoRoll:: +begin_draw() { + clear_region(); + + // Draw in the guide bars. + CGContextSetStrokeColorWithColor(_ctx, [NSColor gridColor].CGColor); + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; ++i) { + draw_guide_bar(_ctx, get_guide_bar(i), 0, get_ysize()); + } +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any one row of bars. These bars correspond to the collector whose + * index is get_row_collector(row), and in the color get_row_color(row). + */ +void MacStatsPianoRoll:: +begin_row(int row) { + int collector_index = get_label_collector(row); + bool is_highlighted = collector_index == _highlighted_index; + CGContextSetFillColorWithColor(_ctx, + MacStatsGraph::_monitor->get_collector_color(collector_index, is_highlighted)); +} + +/** + * Draws a single bar on the chart. + */ +void MacStatsPianoRoll:: +draw_bar(int row, int from_x, int to_x) { + if (row >= 0 && row < _label_stack.get_num_labels()) { + int y = _label_stack.get_label_y(row, _graph_view); + int height = _label_stack.get_label_height(row); + + CGContextFillRect(_ctx, CGRectMake(from_x, (y - height + 2), to_x - from_x, (height - 4))); + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void MacStatsPianoRoll:: +end_draw() { + _graph_view.needsDisplay = YES; + + if (_guide_bars_changed) { + _scale_area.needsDisplay = YES; + _guide_bars_changed = false; + } +} + +/** + * Called at the end of the draw cycle. + */ +void MacStatsPianoRoll:: +idle() { + if (_labels_changed) { + update_labels(); + } +} + +/** + * Returns the current window dimensions. + */ +bool MacStatsPianoRoll:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + MacStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void MacStatsPianoRoll:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + MacStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * Called when the mouse right-clicks on the graph, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsPianoRoll:: +get_graph_menu(int mouse_x, int mouse_y) const { + int collector_index = get_collector_under_pixel(mouse_x, mouse_y); + if (collector_index >= 0) { + return get_label_menu(collector_index); + } + return nil; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsPianoRoll:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + int collector_index = get_collector_under_pixel(mouse_x, mouse_y); + if (collector_index >= 0) { + return get_label_tooltip(collector_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +MacStatsGraph::DragMode MacStatsPianoRoll:: +consider_drag_start(int graph_x, int graph_y) { + if (graph_y >= 0 && graph_y < get_ysize()) { + if (graph_x >= 0 && graph_x < get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = graph_x; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + return MacStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +void MacStatsPianoRoll:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + if (double_click && button == 1) { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + on_click_label(get_collector_under_pixel(graph_x, graph_y)); + return; + } + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_x); + // SetCapture(_graph_window); + return; + + } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_x = graph_x; + // SetCapture(_graph_window); + return; + } + + return MacStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +void MacStatsPianoRoll:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return MacStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsPianoRoll:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int collector_index = get_collector_under_pixel(graph_x, graph_y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + + /* + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + */ + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + double ratio = (double)graph_x / (double)get_xsize(); + if (ratio > 0.0f) { + set_horizontal_scale(_drag_scale_start / ratio); + } + return; + } + else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_x >= 0 && graph_x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x)); + return; + } + + } else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + return; + } + + return MacStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +void MacStatsPianoRoll:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsPianoRoll:: +handle_scroll() { + // Graph view is flipped, side bar isn't, so we need to convert coordinates + NSPoint point; + point.x = 0; + point.y = _graph_view.frame.size.height - (((NSScrollView *)_graph_view_controller.view).documentVisibleRect.size.height + ((NSScrollView *)_graph_view_controller.view).documentVisibleRect.origin.y); + [_sidebar_scroll_view.contentView scrollToPoint:point]; + [_sidebar_scroll_view reflectScrolledClipView:_sidebar_scroll_view.contentView]; +} + +/** + * + */ +void MacStatsPianoRoll:: +handle_magnify(int graph_x, int graph_y, double scale) { + set_horizontal_scale(get_horizontal_scale() * (1.0 - scale)); +} + +/** + * Fills in the graph window. + */ +void MacStatsPianoRoll:: +handle_draw_graph(CGContextRef ctx, NSRect rect) { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 + draw_guide_bars(ctx, rect.origin.y, rect.size.height); +#endif + + // Copy the drawn bars into the graph. + MacStatsGraph::handle_draw_graph(ctx, rect); + + // Draw the scale area. +/* + CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0); + CGContextSetStrokeColor(ctx, rgb_dark_gray); + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, 0, header_height); + CGContextAddLineToPoint(ctx, get_xsize(), header_height); + CGContextStrokePath(ctx);*/ + + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; ++i) { + draw_guide_bar(ctx, get_user_guide_bar(i), rect.origin.y, rect.size.height); + } + + NSRect scale_frame = _scale_area.frame; + NSRect graph_frame = _graph_view.frame; + if (scale_frame.size.width != graph_frame.size.width) { + scale_frame.size.width = graph_frame.size.width; + _scale_area.frame = scale_frame; + } +} + +/** + * Fills in the graph window overhang, which is the area outside the graph + * bounds that may become visible momentarily due to scroll elasticity. + */ +void MacStatsPianoRoll:: +handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) { + CGContextSetFillColorWithColor(ctx, _background_color); + CGContextFillRect(ctx, rect); + + draw_guide_bars(ctx, rect.origin.y, rect.size.height); +} + +/** + * Fills in the scale area. + */ +void MacStatsPianoRoll:: +handle_draw_scale_area(CGContextRef ctx, NSRect rect) { +/* + CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0); + CGContextSetStrokeColor(ctx, rgb_dark_gray); + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, 0, header_height); + CGContextAddLineToPoint(ctx, get_xsize(), header_height); + CGContextStrokePath(ctx);*/ + + draw_guide_bars(ctx, rect.origin.y, rect.size.height); + draw_guide_labels(ctx); +} + +/** + * Returns the collector index associated with the indicated vertical row, or + * -1. + */ +int MacStatsPianoRoll:: +get_collector_under_pixel(int xpoint, int ypoint) const { + if (_label_stack.get_num_labels() == 0) { + return -1; + } + + // Assume all of the labels are the same height. + int origin = _label_stack.get_label_y(0, _graph_view); + int height = _label_stack.get_label_height(0); + int row = (origin - ypoint) / height; + if (row >= 0 && row < _label_stack.get_num_labels()) { + return _label_stack.get_label_collector_index(row); + } else { + return -1; + } +} + +/** + * Resets the list of labels. + */ +void MacStatsPianoRoll:: +update_labels() { + _label_stack.clear_labels(); + for (int i = 0; i < get_num_labels(); ++i) { + _label_stack.add_label(MacStatsGraph::_monitor, this, + _thread_index, + get_label_collector(i), true); + } + _labels_changed = false; +} + +/** + * + */ +void MacStatsPianoRoll:: +draw_guide_bars(CGContextRef ctx, int y, int height) { + CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor); + + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; ++i) { + draw_guide_bar(ctx, get_guide_bar(i), y, height); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; ++i) { + draw_guide_bar(ctx, get_user_guide_bar(i), y, height); + } +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void MacStatsPianoRoll:: +draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar, + int y, int height) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to the top. + /*switch (bar._style) { + case GBS_target: + CGContextSetRGBStrokeColor(ctx, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2], 1.0); + break; + + case GBS_user: + CGContextSetRGBStrokeColor(ctx, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2], 1.0); + break; + + default: + CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0); + break; + }*/ + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, x, y); + CGContextAddLineToPoint(ctx, x, y + height); + CGContextStrokePath(ctx); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void MacStatsPianoRoll:: +draw_guide_labels(CGContextRef ctx) { + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; ++i) { + draw_guide_label(ctx, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; ++i) { + draw_guide_label(ctx, get_user_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void MacStatsPianoRoll:: +draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) { + NSColor *color; + if (@available(macOS 11.0, *)) { + color = [NSColor tertiaryLabelColor]; + } else { + // Otherwise it's hard to see on the dark titlebars + color = [NSColor windowFrameTextColor]; + } + /* + switch (bar._style) { + case GBS_target: + color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0]; + break; + + case GBS_user: + color = [NSColor colorWithDeviceRed:rgb_user_guide_bar[0] green:rgb_user_guide_bar[1] blue:rgb_user_guide_bar[2] alpha:1.0]; + break; + + default: + color = [NSColor colorWithDeviceRed:rgb_dark_gray[0] green:rgb_dark_gray[1] blue:rgb_dark_gray[2] alpha:1.0]; + break; + }*/ + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + color, + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, label.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + CFRelease(attribs); + CFRelease(str); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CFRelease(astr); + CGRect bounds = CTLineGetImageBounds(line, ctx); + int width = bounds.size.width; + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - width); + double to_height = pixel_to_height(x + width); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + CFRelease(line); + return; + } + } + + if (x >= 0 && x < get_xsize()) { + CGContextSetTextPosition(ctx, x + 6, 6); + CTLineDraw(line, ctx); + } + + CFRelease(line); +} diff --git a/pandatool/src/mac-stats/macStatsScaleArea.h b/pandatool/src/mac-stats/macStatsScaleArea.h new file mode 100644 index 00000000..cf7ae2f0 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsScaleArea.h @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsScaleArea.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSSCALEAREA_H +#define MACSTATSSCALEAREA_H + +#import + +class MacStatsGraph; + +@interface MacStatsScaleArea : NSView { + @private + MacStatsGraph *_graph; +} + +- (id)initWithGraph:(MacStatsGraph *)graph frame:(NSRect)rect; +- (void)drawRect:(NSRect)dirtyRect; + +@end + +@interface MacStatsScaleAreaController : NSTitlebarAccessoryViewController { + @private + MacStatsGraph *_graph; +} + +- (id)initWithGraph:(MacStatsGraph *)graph; +- (void)loadView; + +@end + +#endif diff --git a/pandatool/src/mac-stats/macStatsScaleArea.mm b/pandatool/src/mac-stats/macStatsScaleArea.mm new file mode 100644 index 00000000..4502946f --- /dev/null +++ b/pandatool/src/mac-stats/macStatsScaleArea.mm @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsScaleArea.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsScaleArea.h" +#include "macStatsGraph.h" +#include "macStatsStripChart.h" + +@implementation MacStatsScaleArea + +- (id)initWithGraph:(MacStatsGraph *)graph frame:(NSRect)rect { + if (self = [super initWithFrame:rect]) { + _graph = graph; + + [self addTrackingArea:[[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil]]; + } + + return self; +} + +- (void)drawRect:(NSRect)dirtyRect { + _graph->handle_draw_scale_area([NSGraphicsContext currentContext].CGContext, dirtyRect); +} + +@end + +@implementation MacStatsScaleAreaController + +- (id)initWithGraph:(MacStatsGraph *)graph { + if (self = [super init]) { + _graph = graph; + } + + return self; +} + +- (void)loadView { + self.view = [[MacStatsScaleArea alloc] initWithGraph:_graph frame:NSMakeRect(0, 0, 100, 100)]; +} + +@end diff --git a/pandatool/src/mac-stats/macStatsServer.h b/pandatool/src/mac-stats/macStatsServer.h new file mode 100644 index 00000000..8c5ba47e --- /dev/null +++ b/pandatool/src/mac-stats/macStatsServer.h @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsServer.h + * @author rdb + * @date 2023-08-17 + */ + +#ifndef MACSTATSSERVER_H +#define MACSTATSSERVER_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "pStatServer.h" +#include "macStatsMonitor.h" +#include "macStatsAppDelegate.h" + +#include + +/** + * The class that owns the main loop, waiting for client connections. + */ +class MacStatsServer : public PStatServer, public ProgramBase { +public: + MacStatsServer(); + + MacStatsMonitor *get_monitor() { return _monitor; } + + void run(int argc, char *argv[]); + +protected: + virtual bool handle_args(Args &args) override; + + virtual PStatMonitor *make_monitor(const NetAddress &address) override; + virtual void lost_connection(PStatMonitor *monitor) override; + +public: + bool new_session(); + bool open_session(const Filename &fn); + bool open_session(); + bool open_last_session(); + bool save_session(); + bool export_session(); + bool close_session(); + + void set_show_status_item(bool show); + void set_appearance(NSString *name); + void set_time_units(int unit_mask); + +private: + void create_app(); + void setup_session_menu(); + +private: + PT(MacStatsMonitor) _monitor; + + Filename _last_session; + Filename _save_filename; + + int _port = -1; + NSApplication *_app = nil; + NSUserNotification *_listening_notification = nil; + NSMenu *_main_menu = nil; + NSMenuItem *_show_status_item_menu_item = nil; + NSMenuItem *_appearance_system_menu_item = nil; + NSMenuItem *_appearance_aqua_menu_item = nil; + NSMenuItem *_appearance_dark_aqua_menu_item = nil; + NSMenuItem *_units_ms_menu_item = nil; + NSMenuItem *_units_hz_menu_item = nil; + NSMenuItem *_new_session_menu_item = nil; + NSMenuItem *_open_last_session_menu_item = nil; + NSMenuItem *_save_session_menu_item = nil; + NSMenuItem *_close_session_menu_item = nil; + NSMenuItem *_export_session_menu_item = nil; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsServer.mm b/pandatool/src/mac-stats/macStatsServer.mm new file mode 100644 index 00000000..8b7611fe --- /dev/null +++ b/pandatool/src/mac-stats/macStatsServer.mm @@ -0,0 +1,732 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsServer.mm + * @author rdb + * @date 2023-08-17 + */ + +#include "macStatsServer.h" +#include "macStatsMonitor.h" +#include "macStatsAppDelegate.h" +#include "pandaVersion.h" +#include "pStatGraph.h" +#include "config_pstatclient.h" + +#include + +/** + * + */ +MacStatsServer:: +MacStatsServer() : _port(pstats_port) { + set_program_brief("macOS PStats client"); + set_program_description + ("This is a GUI-based PStats server that listens on a TCP port for a " + "connection from a PStatClient in a Panda3D application. It offers " + "various graphs for showing the timing information sent by the client." + "\n\n" + "The full documentation is available online:\n " +#ifdef HAVE_PYTHON + "https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/python/optimization/pstats" +#else + "https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/cpp/optimization/pstats" +#endif + ""); + + add_option + ("p", "port", 0, + "Specify the TCP port to listen for connections on. By default, this " + "is taken from the pstats-port Config variable.", + &ProgramBase::dispatch_int, nullptr, &_port); + + add_runline("[-p 5185]"); + add_runline("session.pstats"); + + _last_session = Filename::expand_from( + "$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats"); + _last_session.set_binary(); + + create_app(); +} + +/** + * Runs the server. + */ +void MacStatsServer:: +run(int argc, char *argv[]) { + if (parse_command_line(argc, argv, isatty(STDERR_FILENO)) == ProgramBase::EC_failure) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = @"Failed to parse command-line options."; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + return; + } + + [_app run]; +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool MacStatsServer:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + // applicationDidFinishLaunching will call new_session(). + return true; + } + else if (args.size() == 1) { + // Handled by application:openFile: + return true; + } + else { + nout << "At most one filename may be specified on the command-line.\n"; + return false; + } +} + +/** + * + */ +PStatMonitor *MacStatsServer:: +make_monitor(const NetAddress &address) { + // Enable the "New Session", "Save Session" and "Close Session" menu items. + _new_session_menu_item.enabled = YES; + _save_session_menu_item.enabled = YES; + _close_session_menu_item.enabled = YES; + _export_session_menu_item.enabled = YES; + + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + if (_listening_notification != nil) { + [center removeDeliveredNotification:_listening_notification]; + [_listening_notification release]; + _listening_notification = nil; + } + + _monitor = new MacStatsMonitor(this); + return _monitor; +} + +/** + * Called when connection has been lost. + */ +void MacStatsServer:: +lost_connection(PStatMonitor *monitor) { + if (_monitor != nullptr && !_monitor->_have_data) { + // We didn't have any data yet. Just silently restart the session. + _monitor->close(); + _monitor = nullptr; + if (new_session()) { + return; + } + } else { + // Store a backup now, in case PStats crashes or something. + _last_session.make_dir(); + if (monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + } else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + stop_listening(); +} + +/** + * Starts a new session. + */ +bool MacStatsServer:: +new_session() { + if (!close_session()) { + return false; + } + + if (listen(_port)) { + NSUserNotification *notification = [[NSUserNotification alloc] init]; + notification.title = @"PStats Server"; + notification.informativeText = [NSString stringWithFormat:@"Waiting for client to connect on port %d\u2026", _port]; + + // Quick way to do something else if the user prefers. + notification.additionalActions = @[ + [NSUserNotificationAction actionWithIdentifier:@"open" title:@"Open Session\u2026"], + [NSUserNotificationAction actionWithIdentifier:@"openLast" title:@"Open Last Session"], + [NSUserNotificationAction actionWithIdentifier:@"quit" title:@"Quit PStats"] + ]; + + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + [center deliverNotification:notification]; + _listening_notification = notification; + + _new_session_menu_item.enabled = NO; + _save_session_menu_item.enabled = NO; + _close_session_menu_item.enabled = YES; + _export_session_menu_item.enabled = NO; + + return true; + } + + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithFormat:@"Unable to open port %d. Try specifying a different port number using pstats-port in your Config file or the -p option on the command-line.", _port]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + + return false; +} + +/** + * Opens a session with the given filename. + */ +bool MacStatsServer:: +open_session(const Filename &fn) { + if (!close_session()) { + return false; + } + + MacStatsMonitor *monitor = new MacStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + return false; + } + + _save_filename = fn; + _new_session_menu_item.enabled = YES; + _save_session_menu_item.enabled = YES; + _close_session_menu_item.enabled = YES; + _export_session_menu_item.enabled = YES; + + _monitor = monitor; + + // If the file contained no graphs, open the default graphs. + if (monitor->_graphs.empty()) { + monitor->open_default_graphs(); + } + + return true; +} + +/** + * Offers to open an existing session. + */ +bool MacStatsServer:: +open_session() { + if (!close_session()) { + return false; + } + + NSOpenPanel *panel = [NSOpenPanel openPanel]; + panel.title = @"Open Session"; + + if ([panel runModal] == NSModalResponseOK) { + NSString *path = [[panel.URLs firstObject] path]; + Filename fn([path UTF8String]); + fn.set_binary(); + if (open_session(fn)) { + return true; + } + + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithFormat:@"Failed to load session file: %s", fn.c_str()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + } + + return false; +} + +/** + * Opens the last session, if any. + */ +bool MacStatsServer:: +open_last_session() { + if (open_session(_last_session)) { + return true; + } + + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithFormat:@"Failed to load session file: %s", _last_session.c_str()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + + return false; +} + +/** + * Offers to save the current session. + */ +bool MacStatsServer:: +save_session() { + nassertr_always(_monitor != nullptr, true); + + NSSavePanel *panel = [NSSavePanel savePanel]; + panel.title = @"Save Session"; + + if (_save_filename.empty()) { + panel.nameFieldStringValue = @"session.pstats"; + } else { + std::string dirname = _save_filename.get_dirname(); + std::string basename = _save_filename.get_basename(); + panel.nameFieldStringValue = [NSString stringWithUTF8String:basename.c_str()]; + panel.directoryURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:dirname.c_str()]]; + } + + if ([panel runModal] == NSModalResponseOK) { + NSString *path = [panel.URL path]; + Filename fn([path UTF8String]); + fn.set_binary(); + + if (!_monitor->write(fn)) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithFormat:@"Failed to save session file: %s", fn.c_str()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + + return false; + } + _save_filename = fn; + _monitor->get_client_data()->clear_dirty(); + return true; + } + + return false; +} + +/** + * Offers to export the current session as a JSON file. + */ +bool MacStatsServer:: +export_session() { + nassertr_always(_monitor != nullptr, true); + + NSSavePanel *panel = [NSSavePanel savePanel]; + panel.title = @"Export Session"; + panel.nameFieldStringValue = @"session.json"; + + if ([panel runModal] == NSModalResponseOK) { + NSString *path = [panel.URL path]; + Filename fn([path UTF8String]); + fn.set_binary(); + + std::ofstream stream; + if (!fn.open_write(stream)) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"PStats Error"; + alert.informativeText = [NSString stringWithFormat:@"Failed to open file for export: %s", fn.c_str()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert runModal]; + [alert release]; + + return false; + } + + int pid = _monitor->get_client_pid(); + _monitor->get_client_data()->write_json(stream, std::max(0, pid)); + stream.close(); + return true; + } + + return false; +} + +/** + * Closes the current session. + */ +bool MacStatsServer:: +close_session() { + bool wrote_last_session = false; + + if (_monitor != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->is_dirty()) { + if (!_monitor->has_read_filename()) { + _last_session.make_dir(); + if (_monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + wrote_last_session = true; + } + else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + // Make sure the alert goes on top. + [_app activateIgnoringOtherApps:YES]; + + NSAlert *alert = [[NSAlert alloc] init]; + alert.messageText = @"Unsaved Data"; + alert.informativeText = @"Would you like to save the currently open session?"; + [alert addButtonWithTitle:@"Save\u2026"]; + [alert addButtonWithTitle:@"Don't Save"]; + [alert addButtonWithTitle:@"Cancel"]; + int response = [alert runModal]; + [alert release]; + + if ((response != 1000 && response != 1001) || + (response == 1000 && !save_session())) { + return false; + } + } + + _monitor->close(); + _monitor = nullptr; + } + + _save_filename = Filename(); + stop_listening(); + + if (_listening_notification != nil) { + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + [center removeDeliveredNotification:_listening_notification]; + [_listening_notification release]; + _listening_notification = nil; + } + + _new_session_menu_item.enabled = YES; + if (wrote_last_session) { + _open_last_session_menu_item.enabled = YES; + } + _save_session_menu_item.enabled = NO; + _close_session_menu_item.enabled = NO; + _export_session_menu_item.enabled = NO; + return true; +} + +/** + * Enables the frame rate label on the right end of the menu bar. This is + * used as a text label to display the main thread's frame rate to the user, + * although it is implemented as a right-justified toplevel menu item that + * doesn't open to anything. + */ +void MacStatsServer:: +set_show_status_item(bool show) { + if (_monitor != nullptr) { + _monitor->set_show_status_item(show); + } + + _show_status_item_menu_item.state = show ? NSOnState : NSOffState; +} + +/** + * + */ +void MacStatsServer:: +set_appearance(NSString *name) { + if (@available(macOS 10.14, *)) { + if (name == nil || name.length == 0) { + [_app setAppearance:nil]; + _appearance_system_menu_item.state = NSOnState; + } else { + [_app setAppearance:[NSAppearance appearanceNamed:name]]; + _appearance_system_menu_item.state = NSOffState; + } + + _appearance_aqua_menu_item.state = (name != nil && [name isEqual:@"NSAppearanceNameAqua"]) ? NSOnState : NSOffState; + _appearance_dark_aqua_menu_item.state = (name != nil && [name isEqual:@"NSAppearanceNameDarkAqua"]) ? NSOnState : NSOffState; + } +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void MacStatsServer:: +set_time_units(int unit_mask) { + if (_monitor != nullptr) { + _monitor->set_time_units(unit_mask); + } + + _units_ms_menu_item.state = (unit_mask & PStatGraph::GBU_ms) ? NSOnState : NSOffState; + _units_hz_menu_item.state = (unit_mask & PStatGraph::GBU_hz) ? NSOnState : NSOffState; +} + +/** + * Creates the menu bar for this monitor. + */ +void MacStatsServer:: +create_app() { + _app = [NSApplication sharedApplication]; + + MacStatsAppDelegate *delegate = [[MacStatsAppDelegate alloc] initWithServer:this]; + [_app setDelegate:delegate]; + [_app setActivationPolicy:NSApplicationActivationPolicyRegular]; + + _main_menu = [[NSMenu alloc] init]; + + NSMenu *app_menu = [[NSMenu alloc] init]; + + NSMenu *settings_menu = [[NSMenu alloc] init]; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleToggleSettingsBool:); + item.representedObject = @"ShowStatusItem"; + item.title = @"Show in Status Bar"; + item.state = NSOnState; + [settings_menu addItem:item]; + + _show_status_item_menu_item = item; + [item release]; + } + + NSMenu *units_menu; + if (@available(macOS 14.0, *)) { + // On Sonnoma, use a section with header. + [settings_menu addItem:[NSMenuItem separatorItem]]; + [settings_menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Time Units"]]; + units_menu = settings_menu; + } else { + // On older versions, create a sub-menu. + units_menu = [[NSMenu alloc] init]; + units_menu.autoenablesItems = NO; + + NSMenuItem *units_item = [[NSMenuItem alloc] init]; + units_item.title = @"Time Units"; + + [settings_menu addItem:units_item]; + [settings_menu setSubmenu:units_menu forItem:units_item]; + [units_item release]; + [units_menu release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSettingsInteger:); + item.representedObject = @"TimeUnits"; + item.tag = PStatGraph::GBU_ms; + item.title = @"ms"; + item.state = NSOnState; + [units_menu addItem:item]; + + _units_ms_menu_item = item; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSettingsInteger:); + item.representedObject = @"TimeUnits"; + item.tag = PStatGraph::GBU_hz; + item.title = @"Hz"; + item.state = NSOffState; + [units_menu addItem:item]; + + _units_hz_menu_item = item; + [item release]; + } + + // Dark mode available from Mojave. + if (@available(macOS 10.14, *)) { + NSMenu *appearance_menu; + if (@available(macOS 14.0, *)) { + // On Sonnoma, use a section with header. + [settings_menu addItem:[NSMenuItem separatorItem]]; + [settings_menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Appearance"]]; + appearance_menu = settings_menu; + } else { + // On older versions, create a sub-menu. + appearance_menu = [[NSMenu alloc] init]; + appearance_menu.autoenablesItems = NO; + + NSMenuItem *appearance_item = [[NSMenuItem alloc] init]; + appearance_item.title = @"Appearance"; + + [settings_menu addItem:appearance_item]; + [settings_menu setSubmenu:appearance_menu forItem:appearance_item]; + [appearance_item release]; + [appearance_menu release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSettingsAppearance:); + item.representedObject = @""; + item.title = @"System"; + item.state = NSOnState; + [appearance_menu addItem:item]; + _appearance_system_menu_item = item; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSettingsAppearance:); + item.representedObject = @"NSAppearanceNameAqua"; + item.title = @"Aqua"; + item.state = NSOffState; + [appearance_menu addItem:item]; + _appearance_aqua_menu_item = item; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSettingsAppearance:); + item.representedObject = @"NSAppearanceNameDarkAqua"; + item.title = @"Dark Aqua"; + item.state = NSOffState; + [appearance_menu addItem:item]; + _appearance_dark_aqua_menu_item = item; + [item release]; + } + } + + NSMenuItem *settings_item = [[NSMenuItem alloc] init]; + settings_item.title = @"Settings"; + [app_menu addItem:settings_item]; + [app_menu setSubmenu:settings_menu forItem:settings_item]; + [settings_item release]; + [settings_menu release]; + + [app_menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(hide:); + item.keyEquivalent = @"h"; + item.title = @"Hide PStats"; + [app_menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(hideOtherApplications:); + item.keyEquivalent = @"h"; + item.keyEquivalentModifierMask |= NSAlternateKeyMask; + item.title = @"Hide Others"; + [app_menu addItem:item]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(unhideAllApplications:); + item.title = @"Show All"; + [app_menu addItem:item]; + [item release]; + } + + [app_menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(terminate:); + item.keyEquivalent = @"q"; + item.title = @"Quit PStats"; + [app_menu addItem:item]; + [item release]; + } + + NSMenuItem *app_menu_item = [[NSMenuItem alloc] init]; + [_main_menu addItem:app_menu_item]; + [_main_menu setSubmenu:app_menu forItem:app_menu_item]; + [app_menu_item release]; + [app_menu release]; + + setup_session_menu(); + + [_app setMainMenu:_main_menu]; + [_main_menu release]; + + //[_app activateIgnoringOtherApps:YES]; + //[_app finishLaunching]; + + set_time_units(PStatGraph::GBU_ms); +} + +/** + * Creates the "Session" pulldown menu. + */ +void MacStatsServer:: +setup_session_menu() { + NSMenu *submenu = [[NSMenu alloc] init]; + submenu.title = @"Session"; + submenu.autoenablesItems = NO; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleNewSession:); + item.keyEquivalent = @"n"; + item.title = @"New Session"; + [submenu addItem:item]; + + _new_session_menu_item = item; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleOpenSession:); + item.keyEquivalent = @"o"; + item.title = @"Open Session\u2026"; + [submenu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleOpenLastSession:); + item.title = @"Open Last Session"; + item.enabled = _last_session.exists(); + [submenu addItem:item]; + + _open_last_session_menu_item = item; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleSaveSession:); + item.keyEquivalent = @"s"; + item.title = @"Save Session\u2026"; + item.enabled = NO; + [submenu addItem:item]; + + _save_session_menu_item = item; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleCloseSession:); + item.keyEquivalent = @"w"; + item.title = @"Close Session"; + item.enabled = NO; + [submenu addItem:item]; + + _close_session_menu_item = item; + [item release]; + } + + [submenu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] init]; + item.action = @selector(handleExportSession:); + item.title = @"Export as JSON\u2026"; + item.enabled = NO; + [submenu addItem:item]; + + _export_session_menu_item = item; + [item release]; + } + + NSMenuItem *item = [[NSMenuItem alloc] init]; + [_main_menu addItem:item]; + [_main_menu setSubmenu:submenu forItem:item]; +} diff --git a/pandatool/src/mac-stats/macStatsStripChart.h b/pandatool/src/mac-stats/macStatsStripChart.h new file mode 100644 index 00000000..685a5f66 --- /dev/null +++ b/pandatool/src/mac-stats/macStatsStripChart.h @@ -0,0 +1,90 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsStripChart.h + * @author rdb + * @date 2023-08-18 + */ + +#ifndef MACSTATSSTRIPCHART_H +#define MACSTATSSTRIPCHART_H + +#include "macStatsGraph.h" +#include "pStatStripChart.h" +#include "macStatsChartMenuDelegate.h" + +class MacStatsMonitor; + +/** + * A window that draws a strip chart, given a view. + */ +class MacStatsStripChart final : public PStatStripChart, public MacStatsGraph { +public: + MacStatsStripChart(MacStatsMonitor *monitor, + int thread_index, int collector_index, bool show_level); + virtual ~MacStatsStripChart(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + virtual void on_click_label(int collector_index); + virtual NSMenu *get_label_menu(int collector_index) const; + virtual std::string get_label_tooltip(int collector_index) const; + void set_vertical_scale(double value_height); + void set_auto_vertical_scale(); + +protected: + virtual void update_labels(); + + virtual void clear_region(); + virtual void draw_slice(int x, int w, + const PStatStripChart::FrameData &fdata); + virtual void draw_empty(int x, int w); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const; + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + virtual void set_drag_mode(DragMode drag_mode); + + virtual void handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual void handle_button_release(int graph_x, int graph_y); + virtual void handle_motion(int graph_x, int graph_y); + virtual void handle_leave(); + virtual void handle_magnify(int graph_x, int graph_y, double scale); + virtual void handle_draw_graph(CGContextRef ctx, NSRect rect); + virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect); + virtual void handle_back(); + +private: + void draw_guide_bar(CGContextRef ctx, int from_x, int to_x, + const PStatGraph::GuideBar &bar); + void draw_guide_labels(CGContextRef ctx); + int draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar, int last_y); + +private: + MacStatsChartMenuDelegate *_menu_delegate; + //NSButton *_smooth_checkbox; + //NSTextField *_total_label; + NSToolbarItem *_total_item; + + std::vector _back_stack; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsStripChart.mm b/pandatool/src/mac-stats/macStatsStripChart.mm new file mode 100644 index 00000000..068c41ea --- /dev/null +++ b/pandatool/src/mac-stats/macStatsStripChart.mm @@ -0,0 +1,964 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsStripChart.mm + * @author rdb + * @date 2023-08-18 + */ + +#include "macStatsStripChart.h" +#include "macStatsMonitor.h" +#include "macStatsScaleArea.h" +#include "pStatCollectorDef.h" +#include "cocoa_compat.h" + +@interface MacStatsStripChartViewController : MacStatsGraphViewController +@end + +static const int default_strip_chart_width = 400; +static const int default_strip_chart_height = 200; + +static const int minimum_strip_chart_sidebar_width = 116; +static const int default_strip_chart_sidebar_width = 116; + +/** + * + */ +MacStatsStripChart:: +MacStatsStripChart(MacStatsMonitor *monitor, int thread_index, + int collector_index, bool show_level) : + PStatStripChart(monitor, thread_index, collector_index, show_level, 0, 0), + MacStatsGraph(monitor, [MacStatsStripChartViewController alloc]) +{ + // Used for popup menus. + _menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index]; + + // Set the initial size of the graph. + int height = default_strip_chart_height + _window.frame.size.height - _window.contentLayoutRect.size.height; + _graph_view.frame = NSMakeRect(0, 0, default_strip_chart_width, height); + _graph_view_controller.view.frame = NSMakeRect(0, 0, default_strip_chart_width, height); + + // It's put inside a scroll view that tracks the main scroll view. + NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_strip_chart_sidebar_width, 0)]; + scroll_view.documentView = _label_stack.get_view(); + scroll_view.drawsBackground = NO; + scroll_view.automaticallyAdjustsContentInsets = YES; + scroll_view.hasHorizontalScroller = NO; + scroll_view.hasVerticalScroller = NO; + + NSViewController *sidebar_controller = [[NSViewController alloc] init]; + sidebar_controller.view = scroll_view; + + // Add a view to the right of the graph, to display all of the scale units. + // Calculate how wide it should be to display a typical label. + CGFloat width = [NSTextField labelWithString:@"999 ms"].frame.size.width + 8; + _scale_area = [[MacStatsScaleArea alloc] initWithGraph:this frame:NSMakeRect(0, 0, width, 0)]; + _scale_area.autoresizingMask = NSViewHeightSizable; + + NSViewController *scale_area_controller = [[NSViewController alloc] init]; + scale_area_controller.view = _scale_area; + + NSSplitViewController *svc = [[NSSplitViewController alloc] init]; + [svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]]; + [svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]]; + [svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:scale_area_controller]]; + + svc.splitViewItems[0].minimumThickness = minimum_strip_chart_sidebar_width; + svc.splitViewItems[2].minimumThickness = width; + svc.splitViewItems[2].maximumThickness = width; + + NSSplitView *split_view = svc.splitView; + split_view.vertical = YES; + split_view.dividerStyle = NSSplitViewDividerStyleThin; + split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _split_view = split_view; + + // When sidebar collapses, show a sidebar icon in the menu bar + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:_graph_view_controller + selector:@selector(handleSplitViewResize:) + name:NSSplitViewDidResizeSubviewsNotification + object:split_view]; + + _window.contentViewController = svc; + + _graph_view_controller.backToolbarItemVisible = NO; + + [svc release]; + [sidebar_controller release]; + + _total_item = nil; + if (@available(macOS 11.0, *)) { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""]; + toolbar.delegate = _graph_view_controller; + toolbar.displayMode = NSToolbarDisplayModeIconOnly; + _window.toolbar = toolbar; + [_window setToolbarStyle:NSWindowToolbarStyleUnifiedCompact]; + + for (NSToolbarItem *item in toolbar.items) { + if ([item.itemIdentifier isEqual:@"total"]) { + _total_item = item; + break; + } + } + [toolbar release]; + } + + /*{ + _smooth_checkbox = [NSButton checkboxWithTitle:@"Smooth" + target:_graph_view_controller + action:@selector(handleToggleAverageMode:)]; + + NSStackView *stack_view = [NSStackView stackViewWithViews:@[_smooth_checkbox]]; + stack_view.translatesAutoresizingMaskIntoConstraints = NO; + + NSTitlebarAccessoryViewController *accessory_controller = [[NSTitlebarAccessoryViewController alloc] init]; + //accessory_controller.layoutAttribute = NSLayoutAttributeLeft; + [accessory_controller.view addSubview:stack_view]; + //accessory_controller.automaticallyAdjustsSize = NO; + [_window addTitlebarAccessoryViewController:accessory_controller]; + [accessory_controller release]; + + [stack_view.leftAnchor constraintEqualToAnchor:_graph_view.leftAnchor constant:8].active = YES; + [stack_view.bottomAnchor constraintEqualToAnchor:((NSLayoutGuide *)_window.contentLayoutGuide).topAnchor constant:-8].active = YES; + //[stack_view.topAnchor constraintEqualToAnchor:svc.view.topAnchor constant:-8].active = YES; + }*/ + /*{ + _total_label = [NSTextField labelWithString:@""]; + + NSStackView *stack_view = [NSStackView stackViewWithViews:@[_total_label]]; + stack_view.translatesAutoresizingMaskIntoConstraints = NO; + + NSTitlebarAccessoryViewController *accessory_controller = [[NSTitlebarAccessoryViewController alloc] init]; + accessory_controller.layoutAttribute = NSLayoutAttributeRight; + [accessory_controller.view addSubview:stack_view]; + //accessory_controller.automaticallyAdjustsSize = NO; + [_window addTitlebarAccessoryViewController:accessory_controller]; + [accessory_controller release]; + + [stack_view.rightAnchor constraintEqualToAnchor:_graph_view.rightAnchor].active = YES; + [stack_view.bottomAnchor constraintEqualToAnchor:((NSLayoutGuide *)_window.contentLayoutGuide).topAnchor].active = YES; + [stack_view.topAnchor constraintEqualToAnchor:svc.view.topAnchor].active = YES; + }*/ + + if (show_level) { + // If it's a level-type graph, show the appropriate units. + if (_unit_name.empty()) { + set_guide_bar_units(GBU_named); + } else { + set_guide_bar_units(GBU_named | GBU_show_units); + } + } else { + // If it's a time-type graph, show the ms / Hz units. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + } + + [_label_stack.get_view().widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES; + + // Update window title and total label. + new_data(0, 0); + + [_window makeKeyAndOrderFront:nil]; +} + +/** + * + */ +MacStatsStripChart:: +~MacStatsStripChart() { + [_menu_delegate release]; +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void MacStatsStripChart:: +new_collector(int collector_index) { + MacStatsGraph::new_collector(collector_index); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void MacStatsStripChart:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + _window.title = [NSString stringWithUTF8String:window_title.c_str()]; + } + } + + if (!_pause) { + update(); + + if (@available(macOS 10.15, *)) { + std::string text = get_total_text(); + [_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]]; + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void MacStatsStripChart:: +force_redraw() { + if (_ctx) { + PStatStripChart::force_redraw(); + } + + _scale_area.needsDisplay = YES; +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void MacStatsStripChart:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatStripChart::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void MacStatsStripChart:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + if (@available(macOS 10.15, *)) { + std::string text = get_total_text(); + [_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]]; + } + + _scale_area.needsDisplay = YES; + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void MacStatsStripChart:: +set_scroll_speed(double scroll_speed) { + // The speed factor indicates chart widths per minute. + if (scroll_speed != 0.0f) { + set_horizontal_scale(60.0f / scroll_speed); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void MacStatsStripChart:: +on_click_label(int collector_index) { + if (collector_index < 0) { + // Clicking on whitespace in the graph is the same as clicking on the top + // label. + collector_index = get_collector_index(); + } + + if (collector_index == get_collector_index()) { + // Clicking on the top label means to go up to the parent level. + if (collector_index != 0) { + const PStatClientData *client_data = + MacStatsGraph::_monitor->get_client_data(); + if (client_data->has_collector(collector_index)) { + const PStatCollectorDef &def = + client_data->get_collector_def(collector_index); + if (def._parent_index == 0 && get_view().get_show_level()) { + // Unless the parent is "Frame", and we're not a time collector. + } + else if (def._parent_index != get_collector_index()) { + // If we were previously at the parent, pop it from the stack. + if (!_back_stack.empty() && _back_stack.back() == def._parent_index) { + _back_stack.pop_back(); + if (_back_stack.empty()) { + _graph_view_controller.backToolbarItemVisible = NO; + } + } else { + if (_back_stack.empty()) { + _graph_view_controller.backToolbarItemVisible = YES; + } + _back_stack.push_back(get_collector_index()); + } + + set_collector_index(def._parent_index); + } + } + } + } + else { + if (_back_stack.empty()) { + _graph_view_controller.backToolbarItemVisible = YES; + } + _back_stack.push_back(get_collector_index()); + + // Clicking on any other label means to focus on that. + set_collector_index(collector_index); + } + + // Update window title and total label. + new_data(0, 0); + + _scale_area.needsDisplay = YES; +} + +/** + * Called when the mouse right-clicks on a label, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsStripChart:: +get_label_menu(int collector_index) const { + NSMenu *menu = [[[NSMenu alloc] init] autorelease]; + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + if (@available(macOS 14.0, *)) { + [menu addItem:[NSMenuItem sectionHeaderWithTitle:[NSString stringWithUTF8String:label.c_str()]]]; + } else { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label.c_str()] action:nil keyEquivalent:@""]; + item.enabled = NO; + [menu addItem:item]; + [item release]; + } + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Set as Focus" action:@selector(handleSetAsFocus:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + item.enabled = (collector_index != 0 || get_collector_index() != 0); + [menu addItem:item]; + [item release]; + } + + { + SEL action = get_view().get_show_level() ? @selector(handleOpenStripChartLevel:) : @selector(handleOpenStripChart:); + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:action keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + if (!get_view().get_show_level()) { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""]; + item.target = _menu_delegate; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Change Color\u2026" action:@selector(handleChangeColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Reset Color" action:@selector(handleResetColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = collector_index; + [menu addItem:item]; + [item release]; + } + + return menu; +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsStripChart:: +get_label_tooltip(int collector_index) const { + return PStatStripChart::get_label_tooltip(collector_index); +} + +/** + * Changes the value the height of the vertical axis represents. This may + * force a redraw. + */ +void MacStatsStripChart:: +set_vertical_scale(double value_height) { + PStatStripChart::set_vertical_scale(value_height); + + _graph_view.needsDisplay = YES; + _scale_area.needsDisplay = YES; +} + +/** + * Sets the vertical scale to make all the data visible. + */ +void MacStatsStripChart:: +set_auto_vertical_scale() { + PStatStripChart::set_auto_vertical_scale(); + set_vertical_scale(get_vertical_scale() * 1.5); + + _graph_view.needsDisplay = YES; + _scale_area.needsDisplay = YES; +} + +/** + * Resets the list of labels. + */ +void MacStatsStripChart:: +update_labels() { + PStatStripChart::update_labels(); + + _label_stack.clear_labels(); + for (int i = 0; i < get_num_labels(); i++) { + _label_stack.add_label(MacStatsGraph::_monitor, this, _thread_index, + get_label_collector(i), false); + } + _labels_changed = false; +} + +/** + * Erases the chart area. + */ +void MacStatsStripChart:: +clear_region() { + if (_ctx) { + //CGContextSetFillColorWithColor(_ctx, _background_color); + //CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize())); + } +} + +/** + * Draws a single vertical slice of the strip chart, at the given pixel + * position, and corresponding to the indicated level data. + */ +void MacStatsStripChart:: +draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) { + if (!_ctx) { + return; + } + + // Start by clearing the band first. + CGContextSetFillColorWithColor(_ctx, _background_color); + CGContextFillRect(_ctx, CGRectMake(x, 0, w, get_ysize())); + + double overall_time = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + overall_time += cd._net_value; + + bool is_highlighted = cd._collector_index == _highlighted_index; + CGContextSetFillColorWithColor(_ctx, + MacStatsGraph::_monitor->get_collector_color(cd._collector_index, is_highlighted)); + + if (overall_time > get_vertical_scale()) { + // Off the top. Go ahead and clamp it by hand, in case it's so far off + // the top we'd overflow the 16-bit pixel value. + CGContextFillRect(_ctx, CGRectMake(x, 0, w, y)); + // And we can consider ourselves done now. + return; + } + + int top_y = height_to_pixel(overall_time); + CGContextFillRect(_ctx, CGRectMake(x, top_y, w, y - top_y)); + y = top_y; + } +} + +/** + * Draws a single vertical slice of background color. + */ +void MacStatsStripChart:: +draw_empty(int x, int w) { + if (!_ctx) { + return; + } + + CGContextSetFillColorWithColor(_ctx, _background_color); + CGContextFillRect(_ctx, CGRectMake(x, 0, w, get_ysize())); +} + +/** + * Draws a single vertical slice of foreground color. + */ +void MacStatsStripChart:: +draw_cursor(int x) { + if (!_ctx) { + return; + } + + CGContextBeginPath(_ctx); + CGContextMoveToPoint(_ctx, x, 0); + CGContextAddLineToPoint(_ctx, x, get_ysize()); + CGContextStrokePath(_ctx); +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the strip chart; it gives the pixel range + * that was just redrawn. + */ +void MacStatsStripChart:: +end_draw(int from_x, int to_x) { + _graph_view.needsDisplay = YES; +} + +/** + * Returns the current window dimensions. + */ +bool MacStatsStripChart:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + MacStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void MacStatsStripChart:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + MacStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * Called when the mouse right-clicks on the graph, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsStripChart:: +get_graph_menu(int mouse_x, int mouse_y) const { + NSMenu *menu = nullptr; + if (_highlighted_index != -1) { + menu = get_label_menu(_highlighted_index); + } +/* + if (menu != nullptr) { + [menu addItem:[NSMenuItem separatorItem]]; + } else { + menu = [[[NSMenu alloc] init] autorelease]; + } + + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Smooth" action:@selector(handleToggleStripChartAverage:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.state = get_average_mode() ? NSOnState : NSOffState; + [menu addItem:item]; + [item release];*/ + return menu; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsStripChart:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + if (_highlighted_index != -1) { + return get_label_tooltip(_highlighted_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +MacStatsGraph::DragMode MacStatsStripChart:: +consider_drag_start(int graph_x, int graph_y) { + // See if the mouse is over a user-defined guide bar. + int y = graph_y; + double from_height = pixel_to_height(y + 2); + double to_height = pixel_to_height(y - 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + return MacStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void MacStatsStripChart:: +set_drag_mode(MacStatsGraph::DragMode drag_mode) { + MacStatsGraph::set_drag_mode(drag_mode); +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +void MacStatsStripChart:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + if (double_click && button == 0) { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + on_click_label(get_collector_under_pixel(graph_x, graph_y)); + return; + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + _drag_scale_start = pixel_to_height(graph_y); + // SetCapture(_graph_window); + return; + } + } + + if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + _drag_start_y = graph_y; + // SetCapture(_graph_window); + return; + } + + return MacStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +void MacStatsStripChart:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_y < 0 || graph_y >= get_ysize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return MacStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsStripChart:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int collector_index = get_collector_under_pixel(graph_x, graph_y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + double ratio = 1.0 - ((double)graph_y / (double)get_ysize()); + if (ratio > 0.0) { + double new_scale = _drag_scale_start / ratio; + if (!IS_NEARLY_EQUAL(get_vertical_scale(), new_scale)) { + // Disable smoothing while we do this expensive operation. + set_vertical_scale(_drag_scale_start / ratio); + } + } + return; + } + else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + if (graph_y >= 0 && graph_y < get_ysize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_y)); + return; + } + } + else if (_drag_mode == DM_guide_bar) { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y)); + return; + } + + MacStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +void MacStatsStripChart:: +handle_leave() { + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + return; +} + +/** + * + */ +void MacStatsStripChart:: +handle_magnify(int graph_x, int graph_y, double scale) { + set_vertical_scale(get_vertical_scale() * (1.0 - scale)); +} + +/** + * Fills in the graph window. + */ +void MacStatsStripChart:: +handle_draw_graph(CGContextRef ctx, NSRect rect) { + MacStatsGraph::handle_draw_graph(ctx, rect); + + int width = get_xsize(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(ctx, 0, width, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(ctx, 0, width, get_user_guide_bar(i)); + } +} + +/** + * Fills in the scale area. + */ +void MacStatsStripChart:: +handle_draw_scale_area(CGContextRef ctx, NSRect rect) { + MacStatsGraph::handle_draw_scale_area(ctx, rect); + draw_guide_labels(ctx); +} + +/** + * Called when the mouse clicks the back button in the toolbar. + */ +void MacStatsStripChart:: +handle_back() { + if (!_back_stack.empty()) { + int collector_index = _back_stack.back(); + _back_stack.pop_back(); + set_collector_index(collector_index); + + // Update window title and total label. + new_data(0, 0); + _scale_area.needsDisplay = YES; + } + + if (_back_stack.empty()) { + _graph_view_controller.backToolbarItemVisible = NO; + } +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void MacStatsStripChart:: +draw_guide_bar(CGContextRef ctx, int from_x, int to_x, + const PStatGraph::GuideBar &bar) { + int y = height_to_pixel(bar._height); + + if (y > 1) { + // Only draw it if it's not too close to the top. + /*switch (bar._style) { + case GBS_target: + CGContextSetRGBStrokeColor(ctx, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2], 1.0); + break; + + case GBS_user: + CGContextSetRGBStrokeColor(ctx, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2], 1.0); + break; + + default: + CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0); + break; + }*/ + CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor); + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, from_x, y); + CGContextAddLineToPoint(ctx, to_x, y); + CGContextStrokePath(ctx); + } +} + +/** + * This is called during the servicing of the draw event. + */ +void MacStatsStripChart:: +draw_guide_labels(CGContextRef ctx) { + // Draw in the labels for the guide bars. + int last_y = 0; + + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + last_y = draw_guide_label(ctx, get_guide_bar(i), last_y); + } + + GuideBar top_value = make_guide_bar(get_vertical_scale()); + draw_guide_label(ctx, top_value, last_y); + + last_y = 0; + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + last_y = draw_guide_label(ctx, get_user_guide_bar(i), last_y); + } +} + +/** + * Draws the text for the indicated guide bar label to the right of the graph, + * unless it would overlap with the indicated last label, whose top pixel + * value is given. Returns the top pixel value of the new label. + */ +int MacStatsStripChart:: +draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar, int last_y) { + NSColor *color; + switch (bar._style) { + case GBS_target: + color = [NSColor secondaryLabelColor]; + break; + + case GBS_user: + color = [NSColor tertiaryLabelColor]; + break; + + default: + color = [NSColor labelColor]; + break; + } + + int y = height_to_pixel(bar._height); + const std::string &label = bar._label; + + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + color, + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, label.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + CFRelease(attribs); + CFRelease(str); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CFRelease(astr); + CGRect bounds = CTLineGetImageBounds(line, ctx); + int height = bounds.size.height; + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(y + height); + double to_height = pixel_to_height(y - height); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + CFRelease(line); + return last_y; + } + } + + if (y > height && y < get_ysize() - height) { + // Now convert our y to a coordinate within our drawing area. + + int this_y = y - height / 2; + if (last_y < this_y || last_y > this_y + height) { + CGContextSetTextPosition(ctx, 4, get_ysize() - this_y - height); + CTLineDraw(line, ctx); + + last_y = this_y; + } + } + + CFRelease(line); + return last_y; +} + +@implementation MacStatsStripChartViewController + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { + if (@available(macOS 10.15, *)) { + return @[@"smooth", @"sep", @"total"]; + } else { + return @[@"smooth", @"sep"]; + } +} + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + if (@available(macOS 10.15, *)) { + return @[@"smooth", @"sep", @"total"]; + } else { + return @[@"smooth", @"sep"]; + } +} + +- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar + itemForItemIdentifier:(NSString *)ident + willBeInsertedIntoToolbar:(BOOL)flag { + + if (@available(macOS 11.0, *)) { + if ([ident isEqual:@"smooth"]) { + NSButton *button = [NSButton buttonWithTitle:@"Smooth" target:self action:@selector(handleToggleSmooth:)]; + button.image = [NSImage imageWithSystemSymbolName:@"alternatingcurrent" accessibilityDescription:@""]; + button.bezelStyle = NSBezelStyleTexturedRounded; + button.buttonType = NSButtonTypePushOnPushOff; + button.bordered = YES; + button.toolTip = @"Smooth"; + button.state = NSOffState; + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease]; + item.label = @"Smooth"; + item.view = button; + return item; + } + if ([ident isEqual:@"total"]) { + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease]; + item.label = @"Total"; + item.action = @selector(handleClickTotal:); + item.target = self; + [item setBordered:YES]; + return item; + } + } + + return [super toolbar:toolbar itemForItemIdentifier:ident willBeInsertedIntoToolbar:flag]; +} + +- (void)handleToggleSmooth:(NSMenuItem *)item { + MacStatsStripChart *graph = (MacStatsStripChart *)_graph; + graph->set_average_mode(item.state == NSOnState); +} + +- (void)handleClickTotal:(NSButton *)button { + MacStatsStripChart *graph = (MacStatsStripChart *)_graph; + graph->set_auto_vertical_scale(); +} + +@end diff --git a/pandatool/src/mac-stats/macStatsTimeline.h b/pandatool/src/mac-stats/macStatsTimeline.h new file mode 100644 index 00000000..e26d5e7c --- /dev/null +++ b/pandatool/src/mac-stats/macStatsTimeline.h @@ -0,0 +1,100 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsTimeline.h + * @author rdb + * @date 2023-08-19 + */ + +#ifndef MACSTATSTIMELINE_H +#define MACSTATSTIMELINE_H + +#include "macStatsGraph.h" +#include "pStatTimeline.h" + +class MacStatsMonitor; + +/** + * A window that draws all of the start/stop event pairs on each thread on a + * horizontal scrolling timeline, with concurrent start/stop pairs stacked + * underneath each other. + */ +class MacStatsTimeline final : public PStatTimeline, public MacStatsGraph { +public: + MacStatsTimeline(MacStatsMonitor *monitor); + virtual ~MacStatsTimeline(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + +protected: + virtual void clear_region(); + virtual void begin_draw(); + virtual void draw_separator(int row); + virtual void draw_guide_bar(int x, GuideBarStyle style); + virtual void draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const; + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int graph_x, int graph_y); + + virtual bool handle_key(int graph_x, int graph_y, bool pressed, + UniChar c, unsigned short key_code); + virtual void handle_button_press(int graph_x, int graph_y, + bool double_click, int button); + virtual void handle_button_release(int graph_x, int graph_y); + virtual void handle_motion(int graph_x, int graph_y); + virtual void handle_leave(); + virtual void handle_scroll(); + virtual void handle_wheel(int graph_x, int graph_y, double dx, double dy); + virtual void handle_magnify(int graph_x, int graph_y, double scale); + virtual void handle_draw_graph(CGContextRef ctx, NSRect rect); + virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect); + virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect); + +public: + void handle_zoom_to(); + void handle_open_strip_chart(); + void handle_open_flame_graph(); + void handle_open_piano_roll(); + +private: + void draw_guide_bar(CGContextRef ctx, GuideBarStyle style, int x, int y, int height); + void draw_guide_labels(CGContextRef ctx); + void draw_guide_label(CGContextRef ctx, const GuideBar &bar); + + int row_to_pixel(int row) const { + return row * 4 * 5 + 4 - _scroll; + } + int pixel_to_row(int y) const { + return (y + _scroll - 4) / (4 * 5); + } + + NSView *_thread_area; + pvector > _thread_labels; + NSLayoutConstraint *_graph_height_constraint; + NSScrollView *_sidebar_scroll_view; + + int _highlighted_row = -1; + int _highlighted_x = 0; + int _scroll = 0; + mutable ColorBar _popup_bar; +}; + +#endif diff --git a/pandatool/src/mac-stats/macStatsTimeline.mm b/pandatool/src/mac-stats/macStatsTimeline.mm new file mode 100644 index 00000000..85f5c89e --- /dev/null +++ b/pandatool/src/mac-stats/macStatsTimeline.mm @@ -0,0 +1,949 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file macStatsTimeline.mm + * @author rdb + * @date 2023-08-19 + */ + +#include "macStatsTimeline.h" +#include "macStatsMonitor.h" +#include "macStatsLabelStack.h" +#include "macStatsScaleArea.h" +#include "pStatCollectorDef.h" + +@interface MacStatsTimelineViewController : MacStatsScrollableGraphViewController +@end + +static const int default_timeline_width = 1000; +static const int default_timeline_height = 300; + +static const int minimum_timeline_sidebar_width = 68; +static const int default_timeline_sidebar_width = 100; + +/** + * + */ +MacStatsTimeline:: +MacStatsTimeline(MacStatsMonitor *monitor) : + PStatTimeline(monitor, 0, 0), + MacStatsGraph(monitor, [MacStatsTimelineViewController alloc]) +{ + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + _window.title = @"Timeline"; + + if (@available(macOS 11.0, *)) { + _window.titleVisibility = NSWindowTitleHidden; + } + + // Set the initial size of the graph. + const ThreadRow &last_thread_row = _threads.back(); + int height = row_to_pixel(last_thread_row._row_offset + last_thread_row._rows.size()); + if (height < default_timeline_height) { + height = default_timeline_height; + } + height += _window.frame.size.height - _window.contentLayoutRect.size.height; + _graph_view.frame = NSMakeRect(0, 0, default_timeline_width, height); + _graph_view_controller.view.frame = NSMakeRect(0, 0, default_timeline_width, height); + + // Add a drawing area to the left of the graph to show the thread labels. + _thread_area = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, default_timeline_sidebar_width, 0)]; + _thread_area.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _thread_area.translatesAutoresizingMaskIntoConstraints = NO; + + // It's put inside a scroll view that tracks the main scroll view. + NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_timeline_sidebar_width, 0)]; + scroll_view.documentView = _thread_area; + scroll_view.drawsBackground = NO; + scroll_view.automaticallyAdjustsContentInsets = YES; + //scroll_view.translatesAutoresizingMaskIntoConstraints = NO; + scroll_view.hasHorizontalScroller = NO; + scroll_view.hasVerticalScroller = NO; + _sidebar_scroll_view = scroll_view; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:_graph_view_controller + selector:@selector(handleSideScroll:) + name:NSScrollViewDidLiveScrollNotification + object:scroll_view]; + + NSViewController *sidebar_controller = [[NSViewController alloc] init]; + sidebar_controller.view = scroll_view; + + NSSplitViewController *svc = [[NSSplitViewController alloc] init]; + [svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]]; + [svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]]; + + svc.splitViewItems[0].minimumThickness = minimum_timeline_sidebar_width; + svc.splitViewItems[0].canCollapse = NO; + + NSSplitView *split_view = svc.splitView; + split_view.vertical = YES; + split_view.dividerStyle = NSSplitViewDividerStyleThin; + split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + _split_view = split_view; + + _window.contentViewController = svc; + + [svc release]; + [sidebar_controller release]; + + // Scale area goes on top, as a titlebar accessory view. + MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this]; + scale_area_controller.fullScreenMinHeight = 20; + scale_area_controller.layoutAttribute = NSLayoutAttributeRight; + _scale_area = scale_area_controller.view; + [_window addTitlebarAccessoryViewController:scale_area_controller]; + [scale_area_controller release]; + + [_thread_area.widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES; + [_thread_area.heightAnchor constraintEqualToAnchor:_graph_view.heightAnchor].active = YES; + + _graph_height_constraint = [_graph_view.heightAnchor constraintGreaterThanOrEqualToConstant:0]; + _graph_height_constraint.active = YES; + + [_window makeKeyAndOrderFront:nil]; +} + +/** + * + */ +MacStatsTimeline:: +~MacStatsTimeline() { + [_thread_area release]; + [_sidebar_scroll_view release]; +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void MacStatsTimeline:: +new_data(int thread_index, int frame_number) { + PStatTimeline::new_data(thread_index, frame_number); +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void MacStatsTimeline:: +force_redraw() { + if (_ctx) { + PStatTimeline::force_redraw(); + } +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void MacStatsTimeline:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatTimeline::changed_size(graph_xsize, graph_ysize); +} + +/** + * Erases the chart area. + */ +void MacStatsTimeline:: +clear_region() { + if (_ctx) { + CGContextSetFillColorWithColor(_ctx, _background_color); + CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize())); + + CGContextSetTextMatrix(_ctx, CGAffineTransformMakeScale(1, -1)); + //draw_guide_labels(_ctx); + } +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void MacStatsTimeline:: +begin_draw() { +} + +/** + * Draws a horizontal separator. + */ +void MacStatsTimeline:: +draw_separator(int row) { + if (_ctx) { + CGContextSetFillColorWithColor(_ctx, [NSColor gridColor].CGColor); + CGContextFillRect(_ctx, CGRectMake(0, (row_to_pixel(row) + row_to_pixel(row + 1)) / 2.0, get_xsize(), 4.0 / 3.0)); + } +} + +/** + * Draws a vertical guide bar. If the row is -1, draws it in all rows. + */ +void MacStatsTimeline:: +draw_guide_bar(int x, GuideBarStyle style) { + draw_guide_bar(_ctx, style, x, 0, get_ysize()); +} + +/** + * Draws a single bar in the chart for the indicated row, in the color for the + * given collector, for the indicated horizontal pixel range. + */ +void MacStatsTimeline:: +draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name) { + int top = row_to_pixel(row); + int bottom = row_to_pixel(row + 1); + int scale = 4; + + top += 1; + + MacStatsMonitor *monitor = MacStatsGraph::_monitor; + + bool is_highlighted = row == _highlighted_row && _highlighted_x >= from_x && _highlighted_x < to_x; + CGContextSetFillColorWithColor(_ctx, + monitor->get_collector_color(collector_index, is_highlighted)); + + if (to_x < from_x + 1) { + // Too tiny to draw. + } + else if (to_x < from_x + scale) { + // It's just a tiny sliver. This is a more reliable way to draw it. + CGRect rect = CGRectMake(from_x, top, to_x - from_x, bottom - top); + CGContextFillRect(_ctx, rect); + } + else { + int left = std::max(from_x, -scale - 1); + int right = std::min(std::max(to_x, from_x + 1), get_xsize() + scale); + + double radius = std::min((double)scale, (right - left) / 2.0); + CGContextBeginPath(_ctx); + CGContextAddArc(_ctx, right - radius - 0.5, top + radius, radius, -0.5 * M_PI, 0.0, NO); + CGContextAddArc(_ctx, right - radius - 0.5, bottom - radius, radius, 0.0, 0.5 * M_PI, NO); + CGContextAddArc(_ctx, left + radius, bottom - radius, radius, 0.5 * M_PI, M_PI, NO); + CGContextAddArc(_ctx, left + radius, top + radius, radius, M_PI, 1.5 * M_PI, NO); + CGContextClosePath(_ctx); + CGContextFillPath(_ctx); + + if ((to_x - from_x) >= scale * 4) { + // Only bother drawing the text if we've got some space to draw on. + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + monitor->get_collector_text_color(collector_index, is_highlighted), + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, collector_name.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CGRect bounds = CTLineGetImageBounds(line, _ctx); + CFRelease(astr); + CFRelease(str); + + int text_width = bounds.size.width; + int text_height = bounds.size.height; + + double center = (from_x + to_x) / 2.0; + double text_left = std::max(from_x, 0) + scale / 2.0; + double text_right = std::min(to_x, get_xsize()) - scale / 2.0; + double text_top = top + (bottom - top - text_height) / 2.0 + text_height; + + if (text_width >= text_right - text_left) { + size_t c = collector_name.rfind(':'); + if (text_right - text_left < scale * 6) { + // It's a really tiny space. Draw a single letter. + UniChar ch = *(collector_name.data() + (c != std::string::npos ? c + 1 : 0)); + + CFStringRef str = CFStringCreateWithCharacters(kCFAllocatorDefault, &ch, 1); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + + CTLineRef new_line = CTLineCreateWithAttributedString((CFAttributedStringRef)astr); + bounds = CTLineGetImageBounds(new_line, _ctx); + text_width = bounds.size.width; + + CFRelease(line); + CFRelease(astr); + CFRelease(str); + line = new_line; + } + else { + // Maybe just use everything after the last colon. + if (c != std::string::npos) { + const char *short_name = collector_name.data() + c + 1; + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, short_name, kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + + CTLineRef new_line = CTLineCreateWithAttributedString((CFAttributedStringRef)astr); + bounds = CTLineGetImageBounds(new_line, _ctx); + text_width = bounds.size.width; + + CFRelease(line); + CFRelease(astr); + CFRelease(str); + line = new_line; + } + } + } + + if (text_width >= text_right - text_left) { + // Have CoreText truncate to the correct length. + static CFStringRef token_str = CFSTR("\u2026"); + CFAttributedStringRef token_astr = CFAttributedStringCreate(kCFAllocatorDefault, token_str, attribs); + CTLineRef token_line = CTLineCreateWithAttributedString(token_astr); + CTLineRef trunc_line = CTLineCreateTruncatedLine(line, text_right - text_left, kCTLineTruncationEnd, token_line); + CFRelease(line); + CFRelease(token_astr); + CFRelease(token_line); + line = trunc_line; + CGContextSetTextPosition(_ctx, text_left, text_top); + } + else if (center - text_width / 2.0 < 0.0) { + // Put it against the left-most edge. + CGContextSetTextPosition(_ctx, scale, text_top); + } + else if (center + text_width / 2.0 >= get_xsize()) { + // Put it against the right-most edge. + CGContextSetTextPosition(_ctx, get_xsize() - scale - text_width, text_top); + } + else { + // It fits just fine, center it. + CGContextSetTextPosition(_ctx, center - text_width / 2.0, text_top); + } + + if (line != nullptr) { + CTLineDraw(line, _ctx); + CFRelease(line); + } + CFRelease(attribs); + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void MacStatsTimeline:: +end_draw() { + // Recalculate the size of the graph. + if (!_threads.empty()) { + const ThreadRow &last_thread_row = _threads.back(); + int new_height = row_to_pixel(last_thread_row._row_offset + last_thread_row._rows.size()); + _graph_height_constraint.constant = new_height; + } + + _graph_view.needsDisplay = YES; + + // If we scroll sideways while we're also scrolling vertically such that the + // overhang becomes visible due to elasticity, the overhang doesn't update. + // I could only find this private method for fixing this problem. + // Not needed as of macOS 14 and up, since the normal drawRect DTRT there. +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 140000 + NSClipView *clip_view = ((MacStatsScrollableGraphViewController *)_graph_view_controller).clipView; + if ([clip_view respondsToSelector:@selector(_setNeedsDisplayInOverhang:)]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-method-access" + [clip_view _setNeedsDisplayInOverhang:YES]; +#pragma clang diagnostic pop + } +#endif + + if (_threads_changed) { + while (_thread_labels.size() < _threads.size()) { + NSTextField *label = [NSTextField labelWithString:@"Thread"]; + label.translatesAutoresizingMaskIntoConstraints = NO; + [_thread_area addSubview:label]; + + [label.rightAnchor constraintEqualToAnchor:_thread_area.rightAnchor constant:-8].active = YES; + [_thread_area.widthAnchor constraintGreaterThanOrEqualToAnchor:label.widthAnchor constant:16].active = YES; + + NSLayoutConstraint *constraint; + if (@available(macOS 11.0, *)) { + constraint = [label.topAnchor constraintEqualToAnchor:_thread_area.topAnchor]; + } else { + constraint = [label.topAnchor constraintEqualToAnchor:_thread_area.topAnchor]; + } + constraint.active = YES; + + _thread_labels.push_back(std::make_pair(label, constraint)); + } + + for (size_t i = 0; i < _threads.size(); ++i) { + const ThreadRow &thread_row = _threads[i]; + NSTextField *label = _thread_labels[i].first; + NSLayoutConstraint *label_constraint = _thread_labels[i].second; + + label.stringValue = [NSString stringWithUTF8String:thread_row._label.c_str()]; + label_constraint.constant = row_to_pixel(thread_row._row_offset); + } + + _thread_area.needsDisplay = YES; + _threads_changed = false; + } + + if (_guide_bars_changed) { + _scale_area.needsDisplay = YES; + _guide_bars_changed = false; + } +} + +/** + * Called at the end of the draw cycle. + */ +void MacStatsTimeline:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool MacStatsTimeline:: +animate(double time, double dt) { + return PStatTimeline::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool MacStatsTimeline:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + MacStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void MacStatsTimeline:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + MacStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * Called when the mouse right-clicks on the graph, and should return the menu + * that should pop up. + */ +NSMenu *MacStatsTimeline:: +get_graph_menu(int graph_x, int graph_y) const { + int row = pixel_to_row(graph_y); + ColorBar bar; + if (!find_bar(row, graph_x, bar)) { + return nil; + } + + _popup_bar = bar; + + NSMenu *menu = [[[NSMenu alloc] init] autorelease]; + + std::string label = get_bar_tooltip(row, graph_x); + if (!label.empty()) { + if (@available(macOS 14.0, *)) { + [menu addItem:[NSMenuItem sectionHeaderWithTitle:[NSString stringWithUTF8String:label.c_str()]]]; + } else { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label.c_str()] action:nil keyEquivalent:@""]; + item.enabled = NO; + [menu addItem:item]; + [item release]; + } + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Zoom To" action:@selector(handleZoomTo:) keyEquivalent:@""]; + item.target = _graph_view_controller; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:@selector(handleOpenStripChart:) keyEquivalent:@""]; + item.target = _graph_view_controller; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""]; + item.target = _graph_view_controller; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Piano Roll" action:@selector(handleOpenPianoRoll:) keyEquivalent:@""]; + item.target = _graph_view_controller; + [menu addItem:item]; + [item release]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Change Color\u2026" action:@selector(handleChangeColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = bar._collector_index; + [menu addItem:item]; + [item release]; + } + + { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Reset Color" action:@selector(handleResetColor:) keyEquivalent:@""]; + item.target = _graph_view_controller; + item.tag = bar._collector_index; + [menu addItem:item]; + [item release]; + } + + return menu; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string MacStatsTimeline:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return PStatTimeline::get_bar_tooltip(pixel_to_row(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the graph window, look for draggable + * things the mouse might be hovering over and return the appropriate DragMode + * enum or DM_none if nothing is indicated. + */ +MacStatsGraph::DragMode MacStatsTimeline:: +consider_drag_start(int graph_x, int graph_y) { + return MacStatsGraph::consider_drag_start(graph_x, graph_y); +} + +/** + * + */ +bool MacStatsTimeline:: +handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) { + // Accept WASD based on their position rather than their mapping + int flag = 0; + switch (key_code) { + case 13: + flag = F_w; + break; + case 0: + flag = F_a; + break; + case 1: + flag = F_s; + break; + case 2: + flag = F_d; + break; + } + if (flag == 0) { + switch (c) { + case 0x1c: + case NSLeftArrowFunctionKey: + flag = F_left; + break; + case 0x1d: + case NSRightArrowFunctionKey: + flag = F_right; + break; + case 'w': + flag = F_w; + break; + case 'a': + flag = F_a; + break; + case 's': + flag = F_s; + break; + case 'd': + flag = F_d; + break; + } + } + if (flag != 0) { + if (pressed) { + if (flag & (F_w | F_s)) { + _zoom_center = pixel_to_timestamp(graph_x); + } + if (_keys_held == 0) { + start_animation(); + } + _keys_held |= flag; + } + else if (_keys_held != 0) { + _keys_held &= ~flag; + } + return true; + } + return false; +} + +/** + * Called when the mouse button is depressed within the graph window. + */ +void MacStatsTimeline:: +handle_button_press(int graph_x, int graph_y, bool double_click, int button) { + if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + if (double_click && button == 0) { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + int row = pixel_to_row(graph_y); + ColorBar bar; + if (find_bar(row, graph_x, bar)) { + double width = bar._end - bar._start; + zoom_to(width * 1.5, pixel_to_timestamp(graph_x)); + scroll_to(bar._start - width / 4.0); + } else { + // Double-clicking the white area zooms out. + _zoom_speed -= 100.0; + } + start_animation(); + } + + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_pan); + _drag_start_x = graph_x; + _scroll_speed = 0.0; + _zoom_center = pixel_to_timestamp(graph_x); + return; + } + } + + return MacStatsGraph::handle_button_press(graph_x, graph_y, + double_click, button); +} + +/** + * Called when the mouse button is released within the graph window. + */ +void MacStatsTimeline:: +handle_button_release(int graph_x, int graph_y) { + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + else if (_drag_mode == DM_guide_bar) { + if (graph_x < 0 || graph_x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x)); + } + set_drag_mode(DM_none); + // ReleaseCapture(); + return handle_motion(graph_x, graph_y); + } + + return MacStatsGraph::handle_button_release(graph_x, graph_y); +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsTimeline:: +handle_motion(int graph_x, int graph_y) { + if (_drag_mode == DM_none && _potential_drag_mode == DM_none && + graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) { + // When the mouse is over a color bar, highlight it. + int row = pixel_to_row(graph_y); + std::swap(_highlighted_x, graph_x); + std::swap(_highlighted_row, row); + + if (row >= 0) { + PStatTimeline::force_redraw(row, graph_x, graph_x); + } + PStatTimeline::force_redraw(_highlighted_row, _highlighted_x, _highlighted_x); + + if ((_keys_held & (F_w | F_s)) != 0) { + // Update the zoom center if we move the mouse while zooming with the + // keyboard. + _zoom_center = pixel_to_timestamp(graph_x); + } + } + else { + // If the mouse is in some drag mode, stop highlighting. + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + } + } + + if (_drag_mode == DM_pan) { + int delta = _drag_start_x - graph_x; + _drag_start_x = graph_x; + set_horizontal_scroll(get_horizontal_scroll() + pixel_to_height(delta)); + return; + } + + return MacStatsGraph::handle_motion(graph_x, graph_y); +} + +/** + * Called when the mouse has left the graph window. + */ +void MacStatsTimeline:: +handle_leave() { + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + } +} + +/** + * Called when the mouse is moved within the graph window. + */ +void MacStatsTimeline:: +handle_scroll() { + // Graph view is flipped, side bar isn't, so we need to convert coordinates + NSPoint point; + point.x = 0; + point.y = _graph_view.frame.size.height - (((NSScrollView *)_graph_view_controller.view).documentVisibleRect.size.height + ((NSScrollView *)_graph_view_controller.view).documentVisibleRect.origin.y); + [_sidebar_scroll_view.contentView scrollToPoint:point]; + [_sidebar_scroll_view reflectScrolledClipView:_sidebar_scroll_view.contentView]; +} + +/** + * + */ +void MacStatsTimeline:: +handle_wheel(int graph_x, int graph_y, double dx, double dy) { + if (dx != 0.0) { + _scroll_speed -= dx; + start_animation(); + } +} + +/** + * + */ +void MacStatsTimeline:: +handle_magnify(int graph_x, int graph_y, double scale) { + zoom_by(scale * 4.0, pixel_to_timestamp(graph_x)); + start_animation(); +} + +/** + * Fills in the graph window. + */ +void MacStatsTimeline:: +handle_draw_graph(CGContextRef ctx, NSRect rect) { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 + for (const GuideBar &bar : _guide_bars) { + int x = timestamp_to_pixel(bar._height); + draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height); + } +#endif + + MacStatsGraph::handle_draw_graph(ctx, rect); + + NSRect scale_frame = _scale_area.frame; + NSRect graph_frame = _graph_view.frame; + if (scale_frame.size.width != graph_frame.size.width) { + scale_frame.size.width = graph_frame.size.width; + _scale_area.frame = scale_frame; + } +} + +/** + * Fills in the graph window overhang, which is the area outside the graph + * bounds that may become visible momentarily due to scroll elasticity. + */ +void MacStatsTimeline:: +handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) { + CGContextSetFillColorWithColor(ctx, _background_color); + CGContextFillRect(ctx, rect); + + for (const GuideBar &bar : _guide_bars) { + int x = timestamp_to_pixel(bar._height); + draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height); + } +} + +/** + * Fills in the scale area. + */ +void MacStatsTimeline:: +handle_draw_scale_area(CGContextRef ctx, NSRect rect) { + MacStatsGraph::handle_draw_scale_area(ctx, rect); + + draw_guide_labels(ctx); + + CGContextSetFillColorWithColor(ctx, [NSColor gridColor].CGColor); + + for (const GuideBar &bar : _guide_bars) { + int x = timestamp_to_pixel(bar._height); + x = [_scale_area convertPoint:NSMakePoint(x, 0) fromView:_graph_view].x; + draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height); + } +} + +/** + * + */ +void MacStatsTimeline:: +handle_zoom_to() { + const ColorBar &bar = _popup_bar; + double width = bar._end - bar._start; + zoom_to(width * 1.5, (bar._end + bar._start) / 2.0); + scroll_to(bar._start - width / 4.0); + start_animation(); +} + +/** + * + */ +void MacStatsTimeline:: +handle_open_strip_chart() { + const ColorBar &bar = _popup_bar; + MacStatsGraph::_monitor->open_strip_chart(bar._thread_index, bar._collector_index, false); +} + +/** + * + */ +void MacStatsTimeline:: +handle_open_flame_graph() { + const ColorBar &bar = _popup_bar; + MacStatsGraph::_monitor->open_flame_graph(bar._thread_index, bar._collector_index, bar._frame_number); +} + +/** + * + */ +void MacStatsTimeline:: +handle_open_piano_roll() { + const ColorBar &bar = _popup_bar; + MacStatsGraph::_monitor->open_piano_roll(bar._thread_index); +} + +/** + * Draws a vertical guide bar. If the row is -1, draws it in all rows. + */ +void MacStatsTimeline:: +draw_guide_bar(CGContextRef ctx, GuideBarStyle style, int x, int y, int height) { + double width = 1.0; + if (style == GBS_frame) { + width *= 2; + } + + CGContextSetFillColorWithColor(ctx, [NSColor gridColor].CGColor); + CGContextFillRect(ctx, CGRectMake(x - width / 2.0, y, width, height)); +} + +/** + * This is called during the servicing of the draw event. + */ +void MacStatsTimeline:: +draw_guide_labels(CGContextRef ctx) { + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_label(ctx, get_guide_bar(i)); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void MacStatsTimeline:: +draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) { + const std::string &label = bar._label; + if (label.empty()) { + return; + } + + NSColor *color; + if (@available(macOS 11.0, *)) { + color = [NSColor tertiaryLabelColor]; + } else { + // Otherwise it's hard to see on the dark titlebars + color = [NSColor windowFrameTextColor]; + } + /*switch (bar._style) { + case GBS_target: + color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0]; + break; + + case GBS_user: + color = [NSColor colorWithDeviceRed:rgb_user_guide_bar[0] green:rgb_user_guide_bar[1] blue:rgb_user_guide_bar[2] alpha:1.0]; + break; + + case GBS_normal: + color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0]; + break; + + case GBS_frame: + color = [NSColor colorWithDeviceRed:rgb_dark_gray[0] green:rgb_dark_gray[1] blue:rgb_dark_gray[2] alpha:1.0]; + break; + }*/ + + const CFStringRef keys[] = { + (__bridge CFStringRef)NSForegroundColorAttributeName, + (__bridge CFStringRef)NSFontAttributeName, + }; + const void *values[] = { + color, + [NSFont systemFontOfSize:0.0], + }; + CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, label.c_str(), kCFStringEncodingUTF8); + CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs); + CFRelease(attribs); + CFRelease(str); + + CTLineRef line = CTLineCreateWithAttributedString(astr); + CFRelease(astr); + CGRect bounds = CTLineGetImageBounds(line, ctx); + //int height = bounds.size.height; + int width = bounds.size.width; + + NSRect graph_bounds = _graph_view.bounds; + + int x = timestamp_to_pixel(bar._height); + x = [_scale_area convertPoint:NSMakePoint(x, 0) fromView:_graph_view].x; + + if (x + width >= 0 && x + width < get_xsize()) { + if (x + width < graph_bounds.size.width) { + CGContextSetTextPosition(ctx, x + 6, 6); + CTLineDraw(line, ctx); + } + } + + CFRelease(line); +} + +@implementation MacStatsTimelineViewController + +- (void)handleZoomTo:(NSMenuItem *)item { + ((MacStatsTimeline *)_graph)->handle_zoom_to(); +} + +- (void)handleOpenStripChart:(NSMenuItem *)item { + ((MacStatsTimeline *)_graph)->handle_open_strip_chart(); +} + +- (void)handleOpenFlameGraph:(NSMenuItem *)item { + ((MacStatsTimeline *)_graph)->handle_open_flame_graph(); +} + +- (void)handleOpenPianoRoll:(NSMenuItem *)item { + ((MacStatsTimeline *)_graph)->handle_open_piano_roll(); +} + +@end + diff --git a/pandatool/src/mac-stats/macstats_composite1.mm b/pandatool/src/mac-stats/macstats_composite1.mm new file mode 100644 index 00000000..5ae8136a --- /dev/null +++ b/pandatool/src/mac-stats/macstats_composite1.mm @@ -0,0 +1,16 @@ +#include "macStats.mm" +#include "macStatsAppDelegate.mm" +#include "macStatsChartMenu.mm" +#include "macStatsChartMenuDelegate.mm" +#include "macStatsFlameGraph.mm" +#include "macStatsGraph.mm" +#include "macStatsGraphView.mm" +#include "macStatsGraphViewController.mm" +#include "macStatsLabel.mm" +#include "macStatsLabelStack.mm" +#include "macStatsMonitor.mm" +#include "macStatsPianoRoll.mm" +#include "macStatsScaleArea.mm" +#include "macStatsServer.mm" +#include "macStatsStripChart.mm" +#include "macStatsTimeline.mm" diff --git a/pandatool/src/miscprogs/CMakeLists.txt b/pandatool/src/miscprogs/CMakeLists.txt new file mode 100644 index 00000000..64ce488e --- /dev/null +++ b/pandatool/src/miscprogs/CMakeLists.txt @@ -0,0 +1,7 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(bin2c binToC.cxx binToC.h) +target_link_libraries(bin2c p3progbase) +install(TARGETS bin2c EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/miscprogs/binToC.cxx b/pandatool/src/miscprogs/binToC.cxx new file mode 100644 index 00000000..ae9fbc40 --- /dev/null +++ b/pandatool/src/miscprogs/binToC.cxx @@ -0,0 +1,149 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file binToC.cxx + * @author drose + * @date 2003-07-18 + */ + +#include "binToC.h" + +// The number of bytes across the page to write. +static const int col_width = 11; + +/** + * + */ +BinToC:: +BinToC() : + WithOutputFile(true, true, false) +{ + clear_runlines(); + add_runline("input output.c"); + add_runline("input -o output.c"); + add_runline("input >output.c"); + + set_program_brief("convert binary data to a compilable C table"); + set_program_description + ("bin2c is a simple utility program to read a disk file, presumably " + "one with binary contents, and output a table that can be " + "compiled via a C compiler to generate the same data. It's handy " + "for portably importing binary data into a library or executable."); + + add_option + ("n", "name", 0, + "Specify the name of the table that is generated.", + &BinToC::dispatch_string, nullptr, &_table_name); + + add_option + ("static", "", 0, + "Flag the table with the keyword 'static'.", + &BinToC::dispatch_none, &_static_table); + + add_option + ("string", "", 0, + "Define the table suitablly to pass to a string constructor.", + &BinToC::dispatch_none, &_for_string); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting C code will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file, or standard output is used if there are no " + "other parameters.", + &BinToC::dispatch_filename, &_got_output_filename, &_output_filename); + + _table_name = "data"; +} + +/** + * + */ +void BinToC:: +run() { + std::ifstream in; + if (!_input_filename.open_read(in)) { + nout << "Unable to read " << _input_filename << ".\n"; + exit(1); + } + + std::ostream &out = get_output(); + std::string static_keyword; + if (_static_table) { + static_keyword = "static "; + } + + std::string table_type = "const unsigned char "; + std::string length_type = "const int "; + if (_for_string) { + // Actually, declaring the table as "const char" causes VC7 to yell about + // truncating all of the values >= 0x80. table_type = "const char "; + length_type = "const size_t "; + } + + out << "\n" + << "/*\n" + << " * This table was generated by the command:\n" + << " *\n" + << " * " << get_exec_command() << "\n" + << " */\n" + << "\n" + << "#include \n" + << "\n" + << static_keyword << table_type << _table_name << "[] = {"; + out << std::hex << std::setfill('0'); + int count = 0; + int col = 0; + int ch = in.get(); + while (!in.fail() && ch != EOF) { + if (col == 0) { + out << "\n "; + } else if (col == col_width) { + out << ",\n "; + col = 0; + } else { + out << ", "; + } + out << "0x" << std::setw(2) << (unsigned int)ch; + col++; + count++; + ch = in.get(); + } + out << "\n};\n\n" + << static_keyword << length_type << _table_name << "_len = " + << std::dec << count << ";\n\n"; +} + +/** + * + */ +bool BinToC:: +handle_args(ProgramBase::Args &args) { + if (args.size() == 2 && !_got_output_filename) { + // The second argument, if present, is implicitly the output file. + _got_output_filename = true; + _output_filename = args[1]; + args.pop_back(); + } + + if (args.size() != 1) { + nout << "You must specify exactly one input file to read on the command line.\n"; + return false; + } + + _input_filename = Filename::binary_filename(args[0]); + return true; +} + + +int main(int argc, char *argv[]) { + BinToC prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/miscprogs/binToC.h b/pandatool/src/miscprogs/binToC.h new file mode 100644 index 00000000..f1ba6599 --- /dev/null +++ b/pandatool/src/miscprogs/binToC.h @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file binToC.h + * @author drose + * @date 2003-07-18 + */ + +#ifndef BINTOC_H +#define BINTOC_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "withOutputFile.h" + +/** + * A utility program to read a (binary) file and output a table that can be + * compiled via a C compiler to generate the same data. Handy for portably + * importing binary data into a library or executable. + */ +class BinToC : public ProgramBase, public WithOutputFile { +public: + BinToC(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; + std::string _table_name; + bool _static_table; + bool _for_string; +}; + +#endif diff --git a/pandatool/src/objegg/CMakeLists.txt b/pandatool/src/objegg/CMakeLists.txt new file mode 100644 index 00000000..c6106815 --- /dev/null +++ b/pandatool/src/objegg/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3OBJEGG_HEADERS + config_objegg.h + eggToObjConverter.h + objToEggConverter.h + objToEggConverter.I +) + +set(P3OBJEGG_SOURCES + config_objegg.cxx + eggToObjConverter.cxx + objToEggConverter.cxx +) + +composite_sources(p3objegg P3OBJEGG_SOURCES) +add_library(p3objegg STATIC ${P3OBJEGG_HEADERS} ${P3OBJEGG_SOURCES}) +target_link_libraries(p3objegg p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/objegg/config_objegg.cxx b/pandatool/src/objegg/config_objegg.cxx new file mode 100644 index 00000000..7896ab30 --- /dev/null +++ b/pandatool/src/objegg/config_objegg.cxx @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_objegg.cxx + * @author drose + * @date 2010-12-07 + */ + +#include "config_objegg.h" +#include "dconfig.h" + +Configure(config_objegg); +NotifyCategoryDef(objegg, ""); + +ConfigureFn(config_objegg) { + init_libobjegg(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libobjegg() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; +} diff --git a/pandatool/src/objegg/config_objegg.h b/pandatool/src/objegg/config_objegg.h new file mode 100644 index 00000000..179d647a --- /dev/null +++ b/pandatool/src/objegg/config_objegg.h @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_objegg.h + * @author drose + * @date 2010-12-07 + */ + +#ifndef CONFIG_OBJEGG_H +#define CONFIG_OBJEGG_H + +#include "pandatoolbase.h" +#include "notifyCategoryProxy.h" + +NotifyCategoryDeclNoExport(objegg); + +extern void init_libobjegg(); + +#endif diff --git a/pandatool/src/objegg/eggToObjConverter.cxx b/pandatool/src/objegg/eggToObjConverter.cxx new file mode 100644 index 00000000..a0363069 --- /dev/null +++ b/pandatool/src/objegg/eggToObjConverter.cxx @@ -0,0 +1,411 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToObjConverter.cxx + * @author drose + * @date 2012-12-19 + */ + +#include "eggToObjConverter.h" +#include "config_objegg.h" +#include "config_egg.h" +#include "eggData.h" +#include "string_utils.h" +#include "streamReader.h" +#include "virtualFileSystem.h" +#include "eggPolygon.h" +#include "eggPoint.h" +#include "eggLine.h" +#include "dcast.h" + +using std::ostream; +using std::string; + +/** + * + */ +EggToObjConverter:: +EggToObjConverter() { +} + +/** + * + */ +EggToObjConverter:: +EggToObjConverter(const EggToObjConverter ©) : + EggToSomethingConverter(copy) +{ +} + +/** + * + */ +EggToObjConverter:: +~EggToObjConverter() { +} + +/** + * Allocates and returns a new copy of the converter. + */ +EggToSomethingConverter *EggToObjConverter:: +make_copy() { + return new EggToObjConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +string EggToObjConverter:: +get_name() const { + return "obj"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +string EggToObjConverter:: +get_extension() const { + return "obj"; +} + +/** + * Returns true if this file type can transparently save compressed files + * (with a .pz extension), false otherwise. + */ +bool EggToObjConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the conversion of the internal EggData to the target file format, + * written to the specified filename. + */ +bool EggToObjConverter:: +write_file(const Filename &filename) { + clear_error(); + + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_zup_right); + } + + if (!process(filename)) { + _error = true; + } + return !had_error(); +} + +/** + * + */ +bool EggToObjConverter:: +process(const Filename &filename) { + _egg_data->flatten_transforms(); + collect_vertices(_egg_data); + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + Filename obj_filename = Filename::text_filename(filename); + vfs->delete_file(obj_filename); + ostream *file = vfs->open_write_file(obj_filename, true, true); + if (file == nullptr) { + return false; + } + if (egg_precision != 0) { + file->precision(egg_precision); + } + + _current_group = nullptr; + + /* + (*file) << "\n#\n" + << "# obj file generated by the following command:\n" + << "# " << get_exec_command() << "\n" + << "#\n\n"; + */ + + write_vertices(*file, "v", 3, _unique_vert3); + write_vertices(*file, "v", 4, _unique_vert4); + write_vertices(*file, "vt", 2, _unique_uv2); + write_vertices(*file, "vt", 3, _unique_uv3); + write_vertices(*file, "vn", 3, _unique_norm); + + write_faces(*file, _egg_data); + + bool success = (file != nullptr); + vfs->close_write_file(file); + + return success; +} + +/** + * Recursively walks the egg structure, looking for vertices referenced by + * polygons or points. Any such vertices are added to the vertex tables for + * writing to the obj file. + */ +void EggToObjConverter:: +collect_vertices(EggNode *egg_node) { + if (egg_node->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node); + EggPrimitive::iterator pi; + for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) { + record_vertex(*pi); + } + + } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); + + EggGroupNode::iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + collect_vertices(*ci); + } + } +} + +/** + * Recursively walks the egg structure again, this time writing out the face + * records for any polygons, points, or lines encountered. + */ +void EggToObjConverter:: +write_faces(ostream &out, EggNode *egg_node) { + if (egg_node->is_of_type(EggPrimitive::get_class_type())) { + const char *prim_type = nullptr; + if (egg_node->is_of_type(EggPolygon::get_class_type())) { + prim_type = "f"; + } else if (egg_node->is_of_type(EggPoint::get_class_type())) { + prim_type = "p"; + } else if (egg_node->is_of_type(EggLine::get_class_type())) { + prim_type = "l"; + } + + if (prim_type != nullptr) { + write_group_reference(out, egg_node); + + EggPrimitive *egg_prim = DCAST(EggPrimitive, egg_node); + + out << prim_type; + EggPrimitive::iterator pi; + for (pi = egg_prim->begin(); pi != egg_prim->end(); ++pi) { + VertexDef &vdef = _vmap[(*pi)]; + int vert_index = -1; + int uv_index = -1; + int norm_index = -1; + + if (vdef._vert3_index != -1) { + vert_index = vdef._vert3_index + 1; + } else if (vdef._vert4_index != -1) { + vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size(); + } + + if (vdef._uv2_index != -1) { + uv_index = vdef._uv2_index + 1; + } else if (vdef._uv3_index != -1) { + uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size(); + } + + if (vdef._norm_index != -1) { + norm_index = vdef._norm_index + 1; + } + + if (vert_index == -1) { + continue; + } + + if (norm_index != -1) { + if (uv_index != -1) { + out << " " << vert_index << "/" << uv_index << "/" << norm_index; + } else { + out << " " << vert_index << "//" << norm_index; + } + } else if (uv_index != -1) { + out << " " << vert_index << "/" << uv_index; + } else { + out << " " << vert_index; + } + } + out << "\n"; + } + } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); + + EggGroupNode::iterator ci; + for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) { + write_faces(out, *ci); + } + } +} + +/** + * Writes the "g" tag to describe this polygon's group, if needed. + */ +void EggToObjConverter:: +write_group_reference(ostream &out, EggNode *egg_node) { + EggGroupNode *egg_group = egg_node->get_parent(); + if (egg_group == _current_group) { + // Same group we wrote last time. + return; + } + + string group_name; + get_group_name(group_name, egg_group); + if (group_name.empty()) { + out << "g default\n"; + } else { + out << "g" << group_name << "\n"; + } + _current_group = egg_group; +} + +/** + * Recursively determines the appropriate string to write for the "g" tag to + * describe a particular EggGroupNode. + */ +void EggToObjConverter:: +get_group_name(string &group_name, EggGroupNode *egg_group) { + string name = trim(egg_group->get_name()); + if (!name.empty()) { + group_name += ' '; + + // Remove nonstandard characters. + for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) { + char c = (*ni); + if (!isalnum(c)) { + c = '_'; + } + group_name += c; + } + } + + // Now recurse. + EggGroupNode *egg_parent = egg_group->get_parent(); + if (egg_parent != nullptr) { + get_group_name(group_name, egg_parent); + } +} + +/** + * Adds the indicated EggVertex to the unique vertex tables, for writing later + * by write_vertices(). + */ +void EggToObjConverter:: +record_vertex(EggVertex *vertex) { + VertexDef &vdef = _vmap[vertex]; + + switch (vertex->get_num_dimensions()) { + case 1: + vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1()); + break; + case 2: + vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2()); + break; + case 3: + vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3()); + break; + case 4: + vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4()); + break; + } + + if (vertex->has_uv("")) { + vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv("")); + } else if (vertex->has_uvw("")) { + vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw("")); + } + + if (vertex->has_normal()) { + vdef._norm_index = record_unique(_unique_norm, vertex->get_normal()); + } +} + +/** + * Records the indicated vertex value, returning the shared index if this + * value already appears elsewhere in the table, or the new unique index if + * this is the first time this value appears. + */ +int EggToObjConverter:: +record_unique(UniqueVertices &unique, const LVecBase4d &vec) { + // We record a zero-based index. Note that we will actually write out a + // one-based index to the obj file, as required by the standard. + int index = unique.size(); + UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first; + return (*ui).second; +} + +/** + * Records the indicated vertex value, returning the shared index if this + * value already appears elsewhere in the table, or the new unique index if + * this is the first time this value appears. + */ +int EggToObjConverter:: +record_unique(UniqueVertices &unique, const LVecBase3d &vec) { + return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0)); +} + +/** + * Records the indicated vertex value, returning the shared index if this + * value already appears elsewhere in the table, or the new unique index if + * this is the first time this value appears. + */ +int EggToObjConverter:: +record_unique(UniqueVertices &unique, const LVecBase2d &vec) { + return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0)); +} + +/** + * Records the indicated vertex value, returning the shared index if this + * value already appears elsewhere in the table, or the new unique index if + * this is the first time this value appears. + */ +int EggToObjConverter:: +record_unique(UniqueVertices &unique, double pos) { + return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0)); +} + +/** + * Actually writes the vertex values recorded in the indicated table to the + * obj output stream. + */ +void EggToObjConverter:: +write_vertices(ostream &out, const string &prefix, int num_components, + const UniqueVertices &unique) { + // First, sort the list into numeric order. + int num_vertices = (int)unique.size(); + const LVecBase4d **vertices = (const LVecBase4d **)PANDA_MALLOC_ARRAY(num_vertices * sizeof(LVecBase4d *)); + memset(vertices, 0, num_vertices * sizeof(LVecBase4d *)); + UniqueVertices::const_iterator ui; + for (ui = unique.begin(); ui != unique.end(); ++ui) { + int index = (*ui).second; + const LVecBase4d &vec = (*ui).first; + nassertv(index >= 0 && index < num_vertices); + nassertv(vertices[index] == nullptr); + vertices[index] = &vec; + } + + for (int i = 0; i < num_vertices; ++i) { + out << prefix; + const LVecBase4d &vec = *(vertices[i]); + for (int ci = 0; ci < num_components; ++ci) { + out << " " << vec[ci]; + } + out << "\n"; + } + PANDA_FREE_ARRAY(vertices); +} + +/** + * + */ +EggToObjConverter::VertexDef:: +VertexDef() : + _vert3_index(-1), + _vert4_index(-1), + _uv2_index(-1), + _uv3_index(-1), + _norm_index(-1) +{ +} diff --git a/pandatool/src/objegg/eggToObjConverter.h b/pandatool/src/objegg/eggToObjConverter.h new file mode 100644 index 00000000..51f940d5 --- /dev/null +++ b/pandatool/src/objegg/eggToObjConverter.h @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToObjConverter.h + * @author drose + * @date 2012-12-19 + */ + +#ifndef EGGTOOBJCONVERTER_H +#define EGGTOOBJCONVERTER_H + +#include "pandatoolbase.h" + +#include "eggToSomethingConverter.h" +#include "eggVertexPool.h" +#include "eggGroup.h" + +/** + * Convert an obj file to egg data. + */ +class EggToObjConverter : public EggToSomethingConverter { +public: + EggToObjConverter(); + EggToObjConverter(const EggToObjConverter ©); + ~EggToObjConverter(); + + virtual EggToSomethingConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + + virtual bool write_file(const Filename &filename); + +private: + typedef pmap UniqueVertices; + class VertexDef { + public: + VertexDef(); + int _vert3_index; + int _vert4_index; + int _uv2_index; + int _uv3_index; + int _norm_index; + }; + typedef pmap VertexMap; + + bool process(const Filename &filename); + + void collect_vertices(EggNode *egg_node); + void write_faces(std::ostream &out, EggNode *egg_node); + void write_group_reference(std::ostream &out, EggNode *egg_node); + void get_group_name(std::string &group_name, EggGroupNode *egg_group); + + void record_vertex(EggVertex *vertex); + int record_unique(UniqueVertices &unique, const LVecBase4d &vec); + int record_unique(UniqueVertices &unique, const LVecBase3d &vec); + int record_unique(UniqueVertices &unique, const LVecBase2d &vec); + int record_unique(UniqueVertices &unique, double pos); + + void write_vertices(std::ostream &out, const std::string &prefix, int num_components, + const UniqueVertices &unique); + +private: + UniqueVertices _unique_vert3, _unique_vert4, _unique_uv2, _unique_uv3, _unique_norm; + VertexMap _vmap; + EggGroupNode *_current_group; +}; + +#endif diff --git a/pandatool/src/objegg/objToEggConverter.I b/pandatool/src/objegg/objToEggConverter.I new file mode 100644 index 00000000..c8a2d629 --- /dev/null +++ b/pandatool/src/objegg/objToEggConverter.I @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file objToEggConverter.I + * @author drose + * @date 2013-01-03 + */ + +/** + * Provides a unique but arbitrary ordering for VertexEntry objects in a map. + */ +INLINE bool ObjToEggConverter::VertexEntry:: +operator < (const VertexEntry &other) const { + if (_vi != other._vi) { + return _vi < other._vi; + } + if (_vti != other._vti) { + return _vti < other._vti; + } + + // It's important that these two tests are made last, so we can find the + // first vertex that has any normal but also matches the above properties. + if (_vni != other._vni) { + return _vni < other._vni; + } + if (_synth_vni != other._synth_vni) { + return _synth_vni < other._synth_vni; + } + return false; +} + +/** + * + */ +INLINE bool ObjToEggConverter::VertexEntry:: +operator == (const VertexEntry &other) const { + return (_vi == other._vi && _vti == other._vti && + _vni == other._vni && _synth_vni == other._synth_vni); +} + +/** + * Returns true if all the properties except _vni and _synth_vni are + * equivalent. + */ +INLINE bool ObjToEggConverter::VertexEntry:: +matches_except_normal(const VertexEntry &other) const { + return (_vi == other._vi && _vti == other._vti); +} diff --git a/pandatool/src/objegg/objToEggConverter.cxx b/pandatool/src/objegg/objToEggConverter.cxx new file mode 100644 index 00000000..7044409c --- /dev/null +++ b/pandatool/src/objegg/objToEggConverter.cxx @@ -0,0 +1,1106 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file objToEggConverter.cxx + * @author drose + * @date 2010-12-07 + */ + +#include "objToEggConverter.h" +#include "config_objegg.h" +#include "eggData.h" +#include "string_utils.h" +#include "streamReader.h" +#include "virtualFileSystem.h" +#include "eggPolygon.h" +#include "nodePath.h" +#include "geomTriangles.h" +#include "geomPoints.h" +#include "colorAttrib.h" +#include "shadeModelAttrib.h" +#include "dcast.h" +#include "triangulator3.h" +#include "config_egg2pg.h" + +using std::string; + +/** + * + */ +ObjToEggConverter:: +ObjToEggConverter() { +} + +/** + * + */ +ObjToEggConverter:: +ObjToEggConverter(const ObjToEggConverter ©) : + SomethingToEggConverter(copy) +{ +} + +/** + * + */ +ObjToEggConverter:: +~ObjToEggConverter() { +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *ObjToEggConverter:: +make_copy() { + return new ObjToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +string ObjToEggConverter:: +get_name() const { + return "obj"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +string ObjToEggConverter:: +get_extension() const { + return "obj"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool ObjToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Returns true if this converter can directly convert the model type to + * internal Panda memory structures, given the indicated options, or false + * otherwise. If this returns true, then convert_to_node() may be called to + * perform the conversion, which may be faster than calling convert_file() if + * the ultimate goal is a PandaNode anyway. + */ +bool ObjToEggConverter:: +supports_convert_to_node(const LoaderOptions &options) const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + */ +bool ObjToEggConverter:: +convert_file(const Filename &filename) { + clear_error(); + + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_zup_right); + } + + if (!process(filename)) { + _error = true; + } + return !had_error(); +} + +/** + * Reads the input file and directly produces a ready-to-render model file as + * a PandaNode. Returns NULL on failure, or if it is not supported. (This + * functionality is not supported by all converter types; see + * supports_convert_to_node()). + */ +PT(PandaNode) ObjToEggConverter:: +convert_to_node(const LoaderOptions &options, const Filename &filename) { + clear_error(); + + _root_node = new PandaNode(""); + _current_vertex_data = new VertexData(_root_node, "root"); + + if (!process_node(filename)) { + _error = true; + } + + _current_vertex_data->close_geom(this); + delete _current_vertex_data; + + if (had_error()) { + return nullptr; + } + + return _root_node; +} + +/** + * Reads the file and converts it to egg structures. + */ +bool ObjToEggConverter:: +process(const Filename &filename) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *strm = vfs->open_read_file(filename, true); + if (strm == nullptr) { + objegg_cat.error() + << "Couldn't read " << filename << "\n"; + return false; + } + + _v_table.clear(); + _vn_table.clear(); + _rgb_table.clear(); + _vt_table.clear(); + _xvt_table.clear(); + _ref_plane_res.set(1.0, 1.0); + _v4_given = false; + _vt3_given = false; + _f_given = false; + + _vpool = new EggVertexPool("vpool"); + _egg_data->add_child(_vpool); + _root_group = new EggGroup("root"); + _egg_data->add_child(_root_group); + _current_group = _root_group; + + StreamReader sr(strm, true); + string line = sr.readline(); + _line_number = 1; + while (!line.empty()) { + line = trim(line); + if (line.empty()) { + line = sr.readline(); + continue; + } + + while (line[line.length() - 1] == '\\') { + // If it ends on a backslash, it's a continuation character. + string line2 = sr.readline(); + ++_line_number; + if (line2.empty()) { + break; + } + line = line.substr(0, line.length() - 1) + trim(line2); + } + + if (line.substr(0, 15) == "#_ref_plane_res") { + process_ref_plane_res(line); + line = sr.readline(); + continue; + } + + if (line[0] == '#') { + line = sr.readline(); + continue; + } + + if (!process_line(line)) { + return false; + } + line = sr.readline(); + ++_line_number; + } + + if (!_f_given) { + generate_egg_points(); + } + + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_line(const string &line) { + vector_string words; + tokenize(line, words, " \t", true); + nassertr(!words.empty(), false); + + string tag = words[0]; + if (tag == "v") { + return process_v(words); + } else if (tag == "vt") { + return process_vt(words); + } else if (tag == "xvt") { + return process_xvt(words); + } else if (tag == "xvc") { + return process_xvc(words); + } else if (tag == "vn") { + return process_vn(words); + } else if (tag == "f") { + return process_f(words); + } else if (tag == "g") { + return process_g(words); + } else { + bool inserted = _ignored_tags.insert(tag).second; + if (inserted) { + objegg_cat.info() + << "Ignoring tag " << tag << "\n"; + } + } + + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_ref_plane_res(const string &line) { + // the #_ref_plane_res line is a DRZ extension that defines the pixel + // resolution of the projector device. It's needed to properly scale the + // xvt lines. + + vector_string words; + tokenize(line, words, " \t", true); + nassertr(!words.empty(), false); + + if (words.size() != 3) { + objegg_cat.error() + << "Wrong number of tokens at line " << _line_number << "\n"; + return false; + } + + bool okflag = true; + okflag &= string_to_double(words[1], _ref_plane_res[0]); + okflag &= string_to_double(words[2], _ref_plane_res[1]); + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << ":\n"; + return false; + } + + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_v(vector_string &words) { + if (words.size() != 4 && words.size() != 5 && + words.size() != 7 && words.size() != 8) { + objegg_cat.error() + << "Wrong number of tokens at line " << _line_number << "\n"; + return false; + } + + bool okflag = true; + LPoint4d pos; + okflag &= string_to_double(words[1], pos[0]); + okflag &= string_to_double(words[2], pos[1]); + okflag &= string_to_double(words[3], pos[2]); + if (words.size() == 5 || words.size() == 8) { + okflag &= string_to_double(words[4], pos[3]); + _v4_given = true; + } else { + pos[3] = 1.0; + } + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << "\n"; + return false; + } + + _v_table.push_back(pos); + + // Meshlab format might include an RGB color following the vertex position. + if (words.size() == 7 && words.size() == 8) { + size_t si = words.size(); + LVecBase3d rgb; + okflag &= string_to_double(words[si - 3], rgb[0]); + okflag &= string_to_double(words[si - 2], rgb[1]); + okflag &= string_to_double(words[si - 1], rgb[2]); + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << "\n"; + return false; + } + while (_rgb_table.size() + 1 < _v_table.size()) { + _rgb_table.push_back(LVecBase3d(1.0, 1.0, 1.0)); + } + _rgb_table.push_back(rgb); + } + + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_vt(vector_string &words) { + if (words.size() != 3 && words.size() != 4) { + objegg_cat.error() + << "Wrong number of tokens at line " << _line_number << "\n"; + return false; + } + + bool okflag = true; + LTexCoord3d uvw; + okflag &= string_to_double(words[1], uvw[0]); + okflag &= string_to_double(words[2], uvw[1]); + if (words.size() == 4) { + okflag &= string_to_double(words[3], uvw[2]); + _vt3_given = true; + } else { + uvw[2] = 0.0; + } + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << "\n"; + return false; + } + + _vt_table.push_back(uvw); + + return true; +} + +/** + * "xvt" is an extended column invented by DRZ. It includes texture + * coordinates in pixel space of the projector device, as well as for each + * camera. We map it to the nominal texture coordinates here. + */ +bool ObjToEggConverter:: +process_xvt(vector_string &words) { + if (words.size() < 3) { + objegg_cat.error() + << "Wrong number of tokens at line " << _line_number << "\n"; + return false; + } + + bool okflag = true; + LTexCoordd uv; + okflag &= string_to_double(words[1], uv[0]); + okflag &= string_to_double(words[2], uv[1]); + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << "\n"; + return false; + } + + uv[0] /= _ref_plane_res[0]; + uv[1] = 1.0 - uv[1] / _ref_plane_res[1]; + + _xvt_table.push_back(uv); + + return true; +} + +/** + * "xvc" is another extended column invented by DRZ. We quietly ignore it. + */ +bool ObjToEggConverter:: +process_xvc(vector_string &words) { + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_vn(vector_string &words) { + if (words.size() != 4) { + objegg_cat.error() + << "Wrong number of tokens at line " << _line_number << "\n"; + return false; + } + + bool okflag = true; + LVector3d normal; + okflag &= string_to_double(words[1], normal[0]); + okflag &= string_to_double(words[2], normal[1]); + okflag &= string_to_double(words[3], normal[2]); + + if (!okflag) { + objegg_cat.error() + << "Invalid number at line " << _line_number << "\n"; + return false; + } + normal.normalize(); + + _vn_table.push_back(normal); + + return true; +} + +/** + * Defines a face in the obj file. + */ +bool ObjToEggConverter:: +process_f(vector_string &words) { + _f_given = true; + + PT(EggPolygon) poly = new EggPolygon; + for (size_t i = 1; i < words.size(); ++i) { + EggVertex *vertex = get_face_vertex(words[i]); + if (vertex == nullptr) { + return false; + } + poly->add_vertex(vertex); + } + _current_group->add_child(poly); + + return true; +} + +/** + * Defines a group in the obj file. + */ +bool ObjToEggConverter:: +process_g(vector_string &words) { + EggGroup *group = _root_group; + + // We assume the group names define a hierarchy of more-specific to less- + // specific group names, so that the first group name is the bottommost + // node, and the last group name is the topmost node. + + // Thus, iterate from the back to the front. + size_t i = words.size(); + while (i > 1) { + --i; + EggNode *child = group->find_child(words[i]); + if (child == nullptr || !child->is_of_type(EggGroup::get_class_type())) { + child = new EggGroup(words[i]); + group->add_child(child); + } + group = DCAST(EggGroup, child); + } + + _current_group = group; + return true; +} + +/** + * Returns or creates a vertex in the vpool according to the indicated face + * reference. + */ +EggVertex *ObjToEggConverter:: +get_face_vertex(const string &reference) { + VertexEntry entry(this, reference); + + // Synthesize a vertex. + EggVertex synth; + + if (entry._vi != 0) { + if (_v4_given) { + synth.set_pos(LCAST(double, _v_table[entry._vi - 1])); + } else { + LPoint4d pos = _v_table[entry._vi - 1]; + synth.set_pos(LPoint3d(pos[0], pos[1], pos[2])); + } + + if (entry._vi - 1 < (int)_rgb_table.size()) { + // We have a per-vertex color too. + LRGBColord rgb = _rgb_table[entry._vi - 1]; + LColor rgba(rgb[0], rgb[1], rgb[2], 1.0); + synth.set_color(rgba); + } + } + + if (entry._vti != 0) { + // We have a texture coordinate; apply it. + if (_vt3_given) { + synth.set_uvw("", _vt_table[entry._vti - 1]); + } else { + LTexCoord3d uvw = _vt_table[entry._vti - 1]; + synth.set_uv("", LTexCoordd(uvw[0], uvw[1])); + } + } else if (entry._vi - 1 < (int)_xvt_table.size()) { + // We have an xvt texture coordinate. + synth.set_uv("", _xvt_table[entry._vi - 1]); + } + + if (entry._vni != 0) { + // We have a normal; apply it. + synth.set_normal(_vn_table[entry._vni - 1]); + } + + return _vpool->create_unique_vertex(synth); +} + +/** + * If an obj file defines no faces, create a bunch of EggVertex objects to + * illustrate the vertex positions at least. + */ +void ObjToEggConverter:: +generate_egg_points() { + for (size_t vi = 0; vi < _v_table.size(); ++vi) { + const LVecBase4d &p = _v_table[vi]; + _vpool->make_new_vertex(LVecBase3d(p[0], p[1], p[2])); + } +} + + +/** + * Reads the file and converts it to PandaNode structures. + */ +bool ObjToEggConverter:: +process_node(const Filename &filename) { + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + std::istream *strm = vfs->open_read_file(filename, true); + if (strm == nullptr) { + objegg_cat.error() + << "Couldn't read " << filename << "\n"; + return false; + } + + _v_table.clear(); + _vn_table.clear(); + _rgb_table.clear(); + _vt_table.clear(); + _xvt_table.clear(); + _ref_plane_res.set(1.0, 1.0); + _v4_given = false; + _vt3_given = false; + _f_given = false; + + StreamReader sr(strm, true); + string line = sr.readline(); + _line_number = 1; + while (!line.empty()) { + line = trim(line); + if (line.empty()) { + line = sr.readline(); + continue; + } + + if (line.substr(0, 15) == "#_ref_plane_res") { + process_ref_plane_res(line); + line = sr.readline(); + continue; + } + + if (line[0] == '#') { + line = sr.readline(); + continue; + } + + if (!process_line_node(line)) { + return false; + } + line = sr.readline(); + ++_line_number; + } + + if (!_f_given) { + generate_points(); + } + + return true; +} + +/** + * + */ +bool ObjToEggConverter:: +process_line_node(const string &line) { + vector_string words; + tokenize(line, words, " \t", true); + nassertr(!words.empty(), false); + + string tag = words[0]; + if (tag == "v") { + return process_v(words); + } else if (tag == "vt") { + return process_vt(words); + } else if (tag == "xvt") { + return process_xvt(words); + } else if (tag == "xvc") { + return process_xvc(words); + } else if (tag == "vn") { + return process_vn(words); + } else if (tag == "f") { + return process_f_node(words); + } else if (tag == "g") { + return process_g_node(words); + } else { + bool inserted = _ignored_tags.insert(tag).second; + if (inserted) { + objegg_cat.info() + << "Ignoring tag " << tag << "\n"; + } + } + + return true; +} + +/** + * Defines a face in the obj file. + */ +bool ObjToEggConverter:: +process_f_node(vector_string &words) { + _f_given = true; + + bool all_vn = true; + //int non_vn_index = -1; + + pvector verts; + verts.reserve(words.size() - 1); + for (size_t i = 1; i < words.size(); ++i) { + VertexEntry entry(this, words[i]); + verts.push_back(entry); + if (entry._vni == 0) { + all_vn = false; + //non_vn_index = i; + } + } + + if (verts.size() < 3) { + // Not enough vertices. + objegg_cat.error() + << "Degenerate face at " << _line_number << "\n"; + return false; + } + + int synth_vni = 0; + if (!all_vn) { + // Synthesize a normal if we need it. + LNormald normal = LNormald::zero(); + for (size_t i = 0; i < verts.size(); ++i) { + int vi0 = verts[i]._vi; + int vi1 = verts[(i + 1) % verts.size()]._vi; + if (vi0 == 0 || vi1 == 0) { + continue; + } + const LVecBase4d &p0 = _v_table[vi0 - 1]; + const LVecBase4d &p1 = _v_table[vi1 - 1]; + + normal[0] += p0[1] * p1[2] - p0[2] * p1[1]; + normal[1] += p0[2] * p1[0] - p0[0] * p1[2]; + normal[2] += p0[0] * p1[1] - p0[1] * p1[0]; + } + normal.normalize(); + synth_vni = add_synth_normal(normal); + } + + Triangulator3 tri; + int num_tris = 1; + + if (verts.size() != 3) { + // We have to triangulate a higher-order polygon. + for (size_t i = 0; i < verts.size(); ++i) { + const LVecBase4d &p = _v_table[verts[i]._vi - 1]; + tri.add_vertex(p[0], p[1], p[2]); + tri.add_polygon_vertex(i); + } + + tri.triangulate(); + num_tris = tri.get_num_triangles(); + } + + if (_current_vertex_data->_prim->get_num_vertices() + 3 * num_tris > egg_max_indices || + _current_vertex_data->_entries.size() + verts.size() > (size_t)egg_max_vertices) { + // We'll exceed our specified limit with these triangles; start a new + // Geom. + _current_vertex_data->close_geom(this); + } + + if (verts.size() == 3) { + // It's already a triangle; add it directly. + _current_vertex_data->add_triangle(this, verts[0], verts[1], verts[2], synth_vni); + + } else { + // Get the triangulated results. + for (int ti = 0; ti < num_tris; ++ti) { + int i0 = tri.get_triangle_v0(ti); + int i1 = tri.get_triangle_v1(ti); + int i2 = tri.get_triangle_v2(ti); + _current_vertex_data->add_triangle(this, verts[i0], verts[i1], verts[i2], synth_vni); + } + } + + return true; +} + +/** + * Defines a group in the obj file. + */ +bool ObjToEggConverter:: +process_g_node(vector_string &words) { + _current_vertex_data->close_geom(this); + delete _current_vertex_data; + _current_vertex_data = nullptr; + + NodePath np(_root_node); + + // We assume the group names define a hierarchy of more-specific to less- + // specific group names, so that the first group name is the bottommost + // node, and the last group name is the topmost node. + + // Thus, iterate from the back to the front. + size_t i = words.size(); + string name; + while (i > 2) { + --i; + name = words[i]; + NodePath child = np.find(name); + if (!child) { + child = np.attach_new_node(name); + } + np = child; + } + + if (i > 1) { + --i; + name = words[i]; + } + + _current_vertex_data = new VertexData(np.node(), name); + + return true; +} + +/** + * If an obj file defines no faces, create a bunch of GeomPoints to illustrate + * the vertex positions at least. + */ +void ObjToEggConverter:: +generate_points() { + CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3(); + PT(GeomVertexData) vdata = new GeomVertexData("points", format, GeomEnums::UH_static); + vdata->set_num_rows(_v_table.size()); + GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex()); + + for (size_t vi = 0; vi < _v_table.size(); ++vi) { + const LVecBase4d &p = _v_table[vi]; + vertex_writer.add_data3d(p[0], p[1], p[2]); + } + + PT(GeomPrimitive) prim = new GeomPoints(GeomEnums::UH_static); + prim->add_next_vertices(_v_table.size()); + prim->close_primitive(); + + PT(Geom) geom = new Geom(vdata); + geom->add_primitive(prim); + + PT(GeomNode) geom_node = new GeomNode("points"); + geom_node->add_geom(geom); + _root_node->add_child(geom_node); +} + +/** + * Adds a new normal to the synth_vn table, or returns an existing normal. In + * either case returns the 1-based index number to the normal. + */ +int ObjToEggConverter:: +add_synth_normal(const LVecBase3d &normal) { + std::pair result = _unique_synth_vn_table.insert(UniqueVec3Table::value_type(normal, _unique_synth_vn_table.size())); + UniqueVec3Table::iterator ni = result.first; + int index = (*ni).second; + + if (result.second) { + // If the normal was added to the table, it's a unique normal, and now we + // have to add it to the table too. + _synth_vn_table.push_back(normal); + } + + return index + 1; +} + +/** + * Creates a VertexEntry from the n/n/n string format in the obj file face + * reference. + */ +ObjToEggConverter::VertexEntry:: +VertexEntry(const ObjToEggConverter *converter, const string &obj_vertex) { + _vi = 0; + _vti = 0; + _vni = 0; + _synth_vni = 0; + + vector_string words; + tokenize(obj_vertex, words, "/", false); + nassertv(!words.empty()); + + for (size_t i = 0; i < words.size(); ++i) { + int index; + if (trim(words[i]).empty()) { + index = 0; + } else { + if (!string_to_int(words[i], index)) { + index = 0; + } + } + + switch (i) { + case 0: + _vi = index; + if (_vi < 0) { + _vi = (int)converter->_v_table.size() + _vi; + } + if (_vi < 0 || _vi - 1 >= (int)converter->_v_table.size()) { + _vi = 0; + } + break; + + case 1: + _vti = index; + if (_vti < 0) { + _vti = (int)converter->_vt_table.size() + _vti; + } + if (_vti < 0 || _vti - 1 >= (int)converter->_vt_table.size()) { + _vti = 0; + } + break; + + case 2: + _vni = index; + if (_vni < 0) { + _vni = (int)converter->_vn_table.size() + _vni; + } + if (_vni < 0 || _vni - 1 >= (int)converter->_vn_table.size()) { + _vni = 0; + } + break; + } + } +} + +/** + * + */ +ObjToEggConverter::VertexData:: +VertexData(PandaNode *parent, const string &name) : + _parent(parent), _name(name) +{ + _geom_node = nullptr; + + _v4_given = false; + _vt3_given = false; + _vt_given = false; + _rgb_given = false; + _vn_given = false; + + _prim = new GeomTriangles(GeomEnums::UH_static); +} + +/** + * Adds a new entry to the vertex data for the indicated VertexEntry, or + * returns an equivalent vertex already present. + */ +int ObjToEggConverter::VertexData:: +add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry) { + std::pair result; + UniqueVertexEntries::iterator ni; + int index; + + if (entry._vni != 0 || entry._synth_vni != 0) { + // If we are storing a vertex with a normal, see if we have already stored + // a vertex without a normal first. + VertexEntry no_normal(entry); + no_normal._vni = 0; + no_normal._synth_vni = 0; + ni = _unique_entries.find(no_normal); + if (ni != _unique_entries.end()) { + // We did have such a vertex! In this case, repurpose this vertex, + // resetting it to contain this normal. + index = (*ni).second; + _unique_entries.erase(ni); + result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, index)); + nassertr(result.second, index); + nassertr(_entries[index] == no_normal, index); + _entries[index]._vni = entry._vni; + _entries[index]._synth_vni = entry._synth_vni; + return index; + } + } else if (entry._vni == 0 && entry._synth_vni == 0) { + // If we are storing a vertex *without* any normal, see if we have already + // stored a vertex with a normal first. + ni = _unique_entries.lower_bound(entry); + if (ni != _unique_entries.end() && (*ni).first.matches_except_normal(entry)) { + // We had such a vertex, so use it. + index = (*ni).second; + return index; + } + } + + // We didn't already have a vertex we could repurpose, so try to add exactly + // the desired vertex. + result = _unique_entries.insert(UniqueVertexEntries::value_type(entry, _entries.size())); + ni = result.first; + index = (*ni).second; + + if (result.second) { + // If the vertex was added to the table, it's a unique vertex, and now we + // have to add it to the vertex data too. + _entries.push_back(entry); + + if (converter->_v4_given) { + _v4_given = true; + } + if (converter->_vt3_given) { + _vt3_given = true; + } + if (entry._vti != 0) { + _vt_given = true; + } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) { + // We have an xvt texture coordinate. + _vt_given = true; + } + if (entry._vi - 1 < (int)converter->_rgb_table.size()) { + // We have a per-vertex color too. + _rgb_given = true; + } + if (entry._vni != 0) { + _vn_given = true; + } + } + + return index; +} + +/** + * Adds a triangle to the primitive, as a triple of three VertexEntry objects, + * which are each added to the vertex pool. If synth_vni is not 0, it is + * assigned to the last vertex. + */ +void ObjToEggConverter::VertexData:: +add_triangle(const ObjToEggConverter *converter, const VertexEntry &v0, + const VertexEntry &v1, const VertexEntry &v2, + int synth_vni) { + int v0i, v1i, v2i; + + v0i = add_vertex(converter, v0); + v1i = add_vertex(converter, v1); + + if (synth_vni != 0) { + VertexEntry v2n(v2); + v2n._synth_vni = synth_vni; + v2i = add_vertex(converter, v2n); + } else { + v2i = add_vertex(converter, v2); + } + + _prim->add_vertices(v0i, v1i, v2i); + _prim->close_primitive(); +} + +/** + * Finishes the current geom and stores it as a child in the root. Prepares + * for new geoms. + */ +void ObjToEggConverter::VertexData:: +close_geom(const ObjToEggConverter *converter) { + if (_prim->get_num_vertices() != 0) { + // Create a new format that includes only the columns we actually used. + PT(GeomVertexArrayFormat) aformat = new GeomVertexArrayFormat; + if (_v4_given) { + aformat->add_column(InternalName::get_vertex(), 4, + GeomEnums::NT_stdfloat, GeomEnums::C_point); + } else { + aformat->add_column(InternalName::get_vertex(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_point); + } + + // We always add normals--if no normals appeared in the file, we + // synthesize them. + aformat->add_column(InternalName::get_normal(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_vector); + + if (_vt_given) { + if (_vt3_given) { + aformat->add_column(InternalName::get_texcoord(), 3, + GeomEnums::NT_stdfloat, GeomEnums::C_texcoord); + } else { + aformat->add_column(InternalName::get_texcoord(), 2, + GeomEnums::NT_stdfloat, GeomEnums::C_texcoord); + } + } + + if (_rgb_given) { + aformat->add_column(InternalName::get_color(), 4, + GeomEnums::NT_uint8, GeomEnums::C_color); + } + + CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(aformat); + + // Create and populate the vertex data. + PT(GeomVertexData) vdata = new GeomVertexData(_name, format, GeomEnums::UH_static); + GeomVertexWriter vertex_writer(vdata, InternalName::get_vertex()); + GeomVertexWriter normal_writer(vdata, InternalName::get_normal()); + GeomVertexWriter texcoord_writer(vdata, InternalName::get_texcoord()); + GeomVertexWriter color_writer(vdata, InternalName::get_color()); + + for (size_t i = 0; i < _entries.size(); ++i) { + const VertexEntry &entry = _entries[i]; + + if (entry._vi != 0) { + vertex_writer.set_row(i); + vertex_writer.add_data4d(converter->_v_table[entry._vi - 1]); + } + if (entry._vti != 0) { + texcoord_writer.set_row(i); + texcoord_writer.add_data3d(converter->_vt_table[entry._vti - 1]); + } else if (entry._vi - 1 < (int)converter->_xvt_table.size()) { + // We have an xvt texture coordinate. + texcoord_writer.set_row(i); + texcoord_writer.add_data2d(converter->_xvt_table[entry._vi - 1]); + } + if (entry._vni != 0) { + normal_writer.set_row(i); + normal_writer.add_data3d(converter->_vn_table[entry._vni - 1]); + } else if (entry._synth_vni != 0) { + normal_writer.set_row(i); + normal_writer.add_data3d(converter->_synth_vn_table[entry._synth_vni - 1]); + } else { + // In this case, the normal isn't used and doesn't matter; we fill it + // in a unit vector just for neatness. + normal_writer.set_row(i); + normal_writer.add_data3d(0, 0, 1); + } + if (_rgb_given) { + if (entry._vi - 1 < (int)converter->_rgb_table.size()) { + color_writer.set_row(i); + color_writer.add_data3d(converter->_rgb_table[entry._vi - 1]); + } + } + } + + // Transform to zup-right. + vdata->transform_vertices(LMatrix4::convert_mat(CS_zup_right, CS_default)); + + // Now create a Geom with this data. + CPT(RenderState) state = RenderState::make_empty(); + if (_rgb_given) { + state = state->add_attrib(ColorAttrib::make_vertex()); + } else { + state = state->add_attrib(ColorAttrib::make_flat(LColor(1, 1, 1, 1))); + } + if (!_vn_given) { + // We have synthesized these normals; specify the flat-shading attrib. + state = state->add_attrib(ShadeModelAttrib::make(ShadeModelAttrib::M_flat)); + _prim->set_shade_model(GeomEnums::SM_flat_last_vertex); + } + + PT(Geom) geom = new Geom(vdata); + geom->add_primitive(_prim); + + if (_geom_node == nullptr) { + _geom_node = new GeomNode(_name); + _parent->add_child(_geom_node); + } + + _geom_node->add_geom(geom, state); + } + + _prim = new GeomTriangles(GeomEnums::UH_static); + _entries.clear(); + _unique_entries.clear(); +} diff --git a/pandatool/src/objegg/objToEggConverter.h b/pandatool/src/objegg/objToEggConverter.h new file mode 100644 index 00000000..528dab8e --- /dev/null +++ b/pandatool/src/objegg/objToEggConverter.h @@ -0,0 +1,149 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file objToEggConverter.h + * @author drose + * @date 2010-12-07 + */ + +#ifndef OBJTOEGGCONVERTER_H +#define OBJTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "somethingToEggConverter.h" +#include "eggVertexPool.h" +#include "eggGroup.h" +#include "geomVertexData.h" +#include "geomVertexWriter.h" +#include "geomPrimitive.h" +#include "geomNode.h" +#include "pandaNode.h" +#include "pvector.h" +#include "epvector.h" + +/** + * Convert an Obj file to egg data. + */ +class ObjToEggConverter : public SomethingToEggConverter { +public: + ObjToEggConverter(); + ObjToEggConverter(const ObjToEggConverter ©); + ~ObjToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + virtual bool supports_convert_to_node(const LoaderOptions &options) const; + + virtual bool convert_file(const Filename &filename); + virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename); + +protected: + bool process(const Filename &filename); + bool process_line(const std::string &line); + bool process_ref_plane_res(const std::string &line); + + bool process_v(vector_string &words); + bool process_vt(vector_string &words); + bool process_xvt(vector_string &words); + bool process_xvc(vector_string &words); + bool process_vn(vector_string &words); + bool process_f(vector_string &words); + bool process_g(vector_string &words); + + EggVertex *get_face_vertex(const std::string &face_reference); + void generate_egg_points(); + + bool process_node(const Filename &filename); + bool process_line_node(const std::string &line); + + bool process_f_node(vector_string &words); + bool process_g_node(vector_string &words); + + void generate_points(); + int add_synth_normal(const LVecBase3d &normal); + + // Read from the obj file. + int _line_number; + typedef epvector Vec4Table; + typedef epvector Vec3Table; + typedef epvector Vec2Table; + typedef pmap UniqueVec3Table; + + Vec4Table _v_table; + Vec3Table _vn_table, _rgb_table; + Vec3Table _vt_table; + Vec2Table _xvt_table; + Vec3Table _synth_vn_table; + UniqueVec3Table _unique_synth_vn_table; + LVecBase2d _ref_plane_res; + bool _v4_given, _vt3_given; + bool _f_given; + + pset _ignored_tags; + + // Structures filled when creating an egg file. + PT(EggVertexPool) _vpool; + PT(EggGroup) _root_group; + EggGroup *_current_group; + + // Structures filled when creating a PandaNode directly. + PT(PandaNode) _root_node; + + class VertexEntry { + public: + VertexEntry(); + VertexEntry(const ObjToEggConverter *converter, const std::string &obj_vertex); + + INLINE bool operator < (const VertexEntry &other) const; + INLINE bool operator == (const VertexEntry &other) const; + INLINE bool matches_except_normal(const VertexEntry &other) const; + + // The 1-based vertex, texcoord, and normal index numbers appearing in the + // obj file for this vertex. 0 if the index number is not given. + int _vi, _vti, _vni; + + // The 1-based index number to the synthesized normal, if needed. + int _synth_vni; + }; + typedef pmap UniqueVertexEntries; + typedef pvector VertexEntries; + + class VertexData { + public: + VertexData(PandaNode *parent, const std::string &name); + + int add_vertex(const ObjToEggConverter *converter, const VertexEntry &entry); + void add_triangle(const ObjToEggConverter *converter, const VertexEntry &v0, + const VertexEntry &v1, const VertexEntry &v2, + int synth_vni); + void close_geom(const ObjToEggConverter *converter); + + PT(PandaNode) _parent; + std::string _name; + PT(GeomNode) _geom_node; + + PT(GeomPrimitive) _prim; + VertexEntries _entries; + UniqueVertexEntries _unique_entries; + + bool _v4_given, _vt3_given; + bool _vt_given, _rgb_given, _vn_given; + }; + + VertexData *_current_vertex_data; + + friend class VertexData; +}; + +#include "objToEggConverter.I" + +#endif diff --git a/pandatool/src/objprogs/CMakeLists.txt b/pandatool/src/objprogs/CMakeLists.txt new file mode 100644 index 00000000..084f140e --- /dev/null +++ b/pandatool/src/objprogs/CMakeLists.txt @@ -0,0 +1,15 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_EGG) + return() +endif() + +add_executable(egg2obj eggToObj.cxx eggToObj.h) +target_link_libraries(egg2obj p3objegg p3eggbase) +install(TARGETS egg2obj EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(obj2egg objToEgg.cxx objToEgg.h) +target_link_libraries(obj2egg p3objegg p3eggbase p3progbase) +install(TARGETS obj2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/objprogs/eggToObj.cxx b/pandatool/src/objprogs/eggToObj.cxx new file mode 100644 index 00000000..aac15220 --- /dev/null +++ b/pandatool/src/objprogs/eggToObj.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToObj.cxx + * @author drose + * @date 2012-02-28 + */ + +#include "eggToObj.h" +#include "eggPolygon.h" +#include "eggGroupNode.h" +#include "dcast.h" +#include "string_utils.h" + +/** + * + */ +EggToObj:: +EggToObj() : + EggToSomething("Obj", ".obj", true, false) +{ + set_program_brief("convert .egg files to .obj"); + set_program_description + ("This program converts egg files to obj. It " + "only converts polygon data, with no fancy tricks. " + "Very bare-bones at the moment; not even texture maps are supported."); + + redescribe_option + ("cs", + "Specify the coordinate system of the resulting " + _format_name + + " file. Normally, this is z-up."); + + add_option + ("C", "", 0, + "Clean out higher-order polygons by subdividing into triangles.", + &EggToObj::dispatch_none, &_triangulate_polygons); + + _coordinate_system = CS_zup_right; + _got_coordinate_system = true; +} + +/** + * + */ +void EggToObj:: +run() { + if (_triangulate_polygons) { + nout << "Triangulating polygons.\n"; + int num_produced = _data->triangulate_polygons(~0); + nout << " (" << num_produced << " triangles produced.)\n"; + } + + EggToObjConverter saver; + saver.set_egg_data(_data); + + if (!saver.write_file(get_output_filename())) { + nout << "An error occurred while writing.\n"; + exit(1); + } +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool EggToObj:: +handle_args(ProgramBase::Args &args) { + return EggToSomething::handle_args(args); +} + +int main(int argc, char *argv[]) { + EggToObj prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/objprogs/eggToObj.h b/pandatool/src/objprogs/eggToObj.h new file mode 100644 index 00000000..b3a50215 --- /dev/null +++ b/pandatool/src/objprogs/eggToObj.h @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToObj.h + * @author drose + * @date 2012-02-25 + */ + +#ifndef EGGTOOBJ_H +#define EGGTOOBJ_H + +#include "pandatoolbase.h" +#include "eggToSomething.h" +#include "eggToObjConverter.h" + +/** + * + */ +class EggToObj : public EggToSomething { +public: + EggToObj(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + +private: + bool _triangulate_polygons; +}; + +#endif diff --git a/pandatool/src/objprogs/objToEgg.cxx b/pandatool/src/objprogs/objToEgg.cxx new file mode 100644 index 00000000..9a46aa71 --- /dev/null +++ b/pandatool/src/objprogs/objToEgg.cxx @@ -0,0 +1,74 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file objToEgg.cxx + * @author drose + * @date 2004-05-04 + */ + +#include "objToEgg.h" + +#include "objToEggConverter.h" + +/** + * + */ +ObjToEgg:: +ObjToEgg() : + SomethingToEgg("obj", ".obj") +{ + add_units_options(); + add_points_options(); + add_normals_options(); + add_transform_options(); + + set_program_brief("convert .obj files to .egg"); + set_program_description + ("This program converts obj files to egg. It " + "only converts polygon data, with no fancy tricks. " + "Very bare-bones at the moment; not even texture maps are supported."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is z-up."); + + _coordinate_system = CS_zup_right; +} + +/** + * + */ +void ObjToEgg:: +run() { + nout << "Reading " << _input_filename << "\n"; + + _data->set_coordinate_system(_coordinate_system); + + ObjToEggConverter converter; + converter.set_egg_data(_data); + converter._allow_errors = _allow_errors; + + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + ObjToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/objprogs/objToEgg.h b/pandatool/src/objprogs/objToEgg.h new file mode 100644 index 00000000..310a5d07 --- /dev/null +++ b/pandatool/src/objprogs/objToEgg.h @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file objToEgg.h + * @author drose + * @date 2010-12-07 + */ + +#ifndef OBJTOEGG_H +#define OBJTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "objToEggConverter.h" + +/** + * A program to read a Obj file and generate an egg file. + */ +class ObjToEgg : public SomethingToEgg { +public: + ObjToEgg(); + + void run(); +}; + +#endif diff --git a/pandatool/src/palettizer/CMakeLists.txt b/pandatool/src/palettizer/CMakeLists.txt new file mode 100644 index 00000000..f3cb2818 --- /dev/null +++ b/pandatool/src/palettizer/CMakeLists.txt @@ -0,0 +1,60 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3PALETTIZER_HEADERS + config_palettizer.h + destTextureImage.h + eggFile.h + filenameUnifier.h + imageFile.h + omitReason.h + paletteGroup.h + paletteGroups.h + paletteImage.h + palettePage.h + palettizer.h + pal_string_utils.h + sourceTextureImage.h + textureImage.h + textureMemoryCounter.h + texturePlacement.h + texturePosition.h + textureProperties.h + textureReference.h + textureRequest.h + txaFile.h + txaLine.h +) + +set(P3PALETTIZER_SOURCES + config_palettizer.cxx + destTextureImage.cxx + eggFile.cxx + filenameUnifier.cxx + imageFile.cxx + omitReason.cxx + paletteGroup.cxx + paletteGroups.cxx + paletteImage.cxx + palettePage.cxx + palettizer.cxx + pal_string_utils.cxx + sourceTextureImage.cxx + textureImage.cxx + textureMemoryCounter.cxx + texturePlacement.cxx + texturePosition.cxx + textureProperties.cxx + textureReference.cxx + textureRequest.cxx + txaFile.cxx + txaLine.cxx +) + +composite_sources(p3palettizer P3PALETTIZER_SOURCES) +add_library(p3palettizer STATIC ${P3PALETTIZER_HEADERS} ${P3PALETTIZER_SOURCES}) +target_link_libraries(p3palettizer p3progbase p3converter) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/palettizer/config_palettizer.cxx b/pandatool/src/palettizer/config_palettizer.cxx new file mode 100644 index 00000000..160fb922 --- /dev/null +++ b/pandatool/src/palettizer/config_palettizer.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_palettizer.cxx + * @author drose + * @date 2003-09-12 + */ + +#include "config_palettizer.h" +#include "palettizer.h" +#include "eggFile.h" +#include "paletteGroup.h" +#include "paletteGroups.h" +#include "textureReference.h" +#include "textureProperties.h" +#include "imageFile.h" +#include "sourceTextureImage.h" +#include "destTextureImage.h" +#include "textureImage.h" +#include "paletteImage.h" +#include "texturePlacement.h" +#include "texturePosition.h" +#include "palettePage.h" + +#include "dconfig.h" + +Configure(config_palettizer); + +ConfigureFn(config_palettizer) { + init_palettizer(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_palettizer() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + Palettizer::init_type(); + EggFile::init_type(); + PaletteGroup::init_type(); + PaletteGroups::init_type(); + TextureReference::init_type(); + TextureProperties::init_type(); + ImageFile::init_type(); + SourceTextureImage::init_type(); + DestTextureImage::init_type(); + TextureImage::init_type(); + PaletteImage::init_type(); + TexturePlacement::init_type(); + TexturePosition::init_type(); + PalettePage::init_type(); + + // Registration of writable object's creation functions with BamReader's + // factory + Palettizer::register_with_read_factory(); + EggFile::register_with_read_factory(); + PaletteGroup::register_with_read_factory(); + PaletteGroups::register_with_read_factory(); + TextureReference::register_with_read_factory(); + TextureProperties::register_with_read_factory(); + SourceTextureImage::register_with_read_factory(); + DestTextureImage::register_with_read_factory(); + TextureImage::register_with_read_factory(); + PaletteImage::register_with_read_factory(); + TexturePlacement::register_with_read_factory(); + TexturePosition::register_with_read_factory(); + PalettePage::register_with_read_factory(); +} diff --git a/pandatool/src/palettizer/config_palettizer.h b/pandatool/src/palettizer/config_palettizer.h new file mode 100644 index 00000000..afb2031d --- /dev/null +++ b/pandatool/src/palettizer/config_palettizer.h @@ -0,0 +1,21 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_palettizer.h + * @author drose + * @date 2003-09-12 + */ + +#ifndef CONFIG_PALETTIZER_H +#define CONFIG_PALETTIZER_H + +#include "pandatoolbase.h" + +void init_palettizer(); + +#endif /* CONFIG_PALETTIZER_H */ diff --git a/pandatool/src/palettizer/destTextureImage.cxx b/pandatool/src/palettizer/destTextureImage.cxx new file mode 100644 index 00000000..3c80f3d9 --- /dev/null +++ b/pandatool/src/palettizer/destTextureImage.cxx @@ -0,0 +1,161 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file destTextureImage.cxx + * @author drose + * @date 2000-12-05 + */ + +#include "destTextureImage.h" +#include "sourceTextureImage.h" +#include "texturePlacement.h" +#include "textureImage.h" +#include "palettizer.h" + +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" + +TypeHandle DestTextureImage::_type_handle; + + +/** + * The default constructor is only for the convenience of the Bam reader. + */ +DestTextureImage:: +DestTextureImage() { +} + +/** + * + */ +DestTextureImage:: +DestTextureImage(TexturePlacement *placement) { + TextureImage *texture = placement->get_texture(); + _properties = texture->get_properties(); + _size_known = texture->is_size_known(); + if (_size_known) { + _x_size = texture->get_x_size(); + _y_size = texture->get_y_size(); + + if (pal->_force_power_2) { + _x_size = to_power_2(_x_size); + _y_size = to_power_2(_y_size); + } else { + _x_size = std::max(_x_size, 1); + _y_size = std::max(_y_size, 1); + } + } + + set_filename(placement->get_group(), texture->get_name()); +} + +/** + * Unconditionally copies the source texture into the appropriate filename. + */ +void DestTextureImage:: +copy(TextureImage *texture) { + const PNMImage &source_image = texture->read_source_image(); + if (source_image.is_valid()) { + PNMImage dest_image(_x_size, _y_size, texture->get_num_channels(), + source_image.get_maxval()); + dest_image.quick_filter_from(source_image); + write(dest_image); + + } else { + // Couldn't read the texture, so fill it with red. + PNMImage dest_image(_x_size, _y_size, texture->get_num_channels()); + dest_image.fill(1.0, 0.0, 0.0); + if (dest_image.has_alpha()) { + dest_image.alpha_fill(1.0); + } + + write(dest_image); + } + + texture->release_source_image(); +} + +/** + * Copies the source texture into the appropriate filename only if the + * indicated old reference, which represents the way it was last copied, is + * now out-of-date. + */ +void DestTextureImage:: +copy_if_stale(const DestTextureImage *other, TextureImage *texture) { + if (other->get_x_size() != get_x_size() || + other->get_y_size() != get_y_size() || + other->get_num_channels() != get_num_channels()) { + copy(texture); + + } else { + // Also check the timestamps. + SourceTextureImage *source = texture->get_preferred_source(); + + if (source != nullptr && + source->get_filename().compare_timestamps(get_filename()) > 0) { + copy(texture); + } + } +} + +/** + * Returns the largest power of 2 less than or equal to value. + */ +int DestTextureImage:: +to_power_2(int value) { + int x = 1; + while ((x << 1) <= value) { + x = (x << 1); + } + return x; +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void DestTextureImage:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void DestTextureImage:: +write_datagram(BamWriter *writer, Datagram &datagram) { + ImageFile::write_datagram(writer, datagram); +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *DestTextureImage:: +make_from_bam(const FactoryParams ¶ms) { + DestTextureImage *me = new DestTextureImage; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void DestTextureImage:: +fillin(DatagramIterator &scan, BamReader *manager) { + ImageFile::fillin(scan, manager); +} diff --git a/pandatool/src/palettizer/destTextureImage.h b/pandatool/src/palettizer/destTextureImage.h new file mode 100644 index 00000000..860655b6 --- /dev/null +++ b/pandatool/src/palettizer/destTextureImage.h @@ -0,0 +1,74 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file destTextureImage.h + * @author drose + * @date 2000-12-05 + */ + +#ifndef DESTTEXTUREIMAGE_H +#define DESTTEXTUREIMAGE_H + +#include "pandatoolbase.h" + +#include "imageFile.h" + +class TexturePlacement; +class TextureImage; + +/** + * This represents a texture filename as it has been resized and copied to the + * map directory (e.g. for an unplaced texture). + */ +class DestTextureImage : public ImageFile { +private: + DestTextureImage(); + +public: + DestTextureImage(TexturePlacement *placement); + + void copy(TextureImage *texture); + void copy_if_stale(const DestTextureImage *other, TextureImage *texture); + +private: + static int to_power_2(int value); + + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ImageFile::init_type(); + register_type(_type_handle, "DestTextureImage", + ImageFile::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream & +operator << (std::ostream &out, const DestTextureImage &dest) { + dest.output_filename(out); + return out; +} + +#endif diff --git a/pandatool/src/palettizer/eggFile.cxx b/pandatool/src/palettizer/eggFile.cxx new file mode 100644 index 00000000..c47eae2f --- /dev/null +++ b/pandatool/src/palettizer/eggFile.cxx @@ -0,0 +1,808 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggFile.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "eggFile.h" +#include "textureImage.h" +#include "paletteGroup.h" +#include "texturePlacement.h" +#include "textureReference.h" +#include "sourceTextureImage.h" +#include "palettizer.h" +#include "filenameUnifier.h" + +#include "eggData.h" +#include "eggGroup.h" +#include "eggTextureCollection.h" +#include "eggComment.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "executionEnvironment.h" +#include "dSearchPath.h" +#include "indirectLess.h" + +#include + +TypeHandle EggFile::_type_handle; + +/** + * + */ +EggFile:: +EggFile() { + _data = nullptr; + _first_txa_match = false; + _default_group = nullptr; + _is_surprise = true; + _is_stale = true; + _had_data = false; +} + +/** + * Accepts the information about the egg file as supplied from the command + * line. Returns true if the egg file is valid, false otherwise. + */ +bool EggFile:: +from_command_line(EggData *data, + const Filename &source_filename, + const Filename &dest_filename, + const std::string &egg_comment) { + _data = data; + _had_data = true; + remove_backstage(_data); + + // We save the current directory at the time the egg file appeared on the + // command line, so that we'll later be able to properly resolve external + // references (like textures) that might be relative to this directory. + _current_directory = ExecutionEnvironment::get_cwd(); + _source_filename = source_filename; + _source_filename.make_absolute(); + _dest_filename = dest_filename; + _dest_filename.make_absolute(); + + // We also save the command line that loaded this egg file, so we can + // continue to write it as a comment to the beginning of the egg file, + // should we need to rewrite it later. + _egg_comment = egg_comment; + + // We save the default PaletteGroup at this point, because the egg file + // inherits the default group that was in effect when it was specified on + // the command line. + _default_group = pal->get_default_group(); + + return true; +} + +/** + * Returns the filename this egg file was read from. + */ +const Filename &EggFile:: +get_source_filename() const { + return _source_filename; +} + + +/** + * Scans the egg file for texture references and updates the _textures list + * appropriately. This assumes the egg file was supplied on the command line + * and thus the _data member is available. + */ +void EggFile:: +scan_textures() { + nassertv(_data != nullptr); + + // Extract the set of textures referenced by this egg file. + EggTextureCollection tc; + tc.find_used_textures(_data); + + // Make sure each tref name is unique within a given file. + tc.uniquify_trefs(); + + // Now build up a list of new TextureReference objects that represent the + // textures actually used and their uv range, etc. + Textures new_textures; + + EggTextureCollection::iterator eti; + for (eti = tc.begin(); eti != tc.end(); ++eti) { + EggTexture *egg_tex = (*eti); + + TextureReference *ref = new TextureReference; + ref->from_egg(this, _data, egg_tex); + + if (!ref->has_uvs()) { + // This texture isn't *really* referenced. (Usually this happens if the + // texture is only referenced by "backstage" geometry, which we don't + // care about.) + delete ref; + + } else { + new_textures.push_back(ref); + } + } + + // Sort the new references into order so we can compare them with the + // original references. + sort(new_textures.begin(), new_textures.end(), + IndirectLess()); + + // Sort the original references too. This should already be sorted from the + // previous run, but we might as well be neurotic about it. + sort(_textures.begin(), _textures.end(), + IndirectLess()); + + // Now go through and merge the lists. + Textures combined_textures; + Textures::const_iterator ai = _textures.begin(); + Textures::const_iterator bi = new_textures.begin(); + + while (ai != _textures.end() && bi != new_textures.end()) { + TextureReference *aref = (*ai); + TextureReference *bref = (*bi); + + if ((*aref) < (*bref)) { + // Here's a texture reference in the original list, but not in the new + // list. Remove it. + delete aref; + ++ai; + + } else if ((*bref) < (*aref)) { + // Here's a texture reference in the new list, but not in the original + // list. Add it. + combined_textures.push_back(bref); + ++bi; + + } else { // (*bref) == (*aref) + // Here's a texture reference that was in both lists. Compare it. + if (aref->is_equivalent(*bref)) { + // It hasn't changed substantially, so keep the original (which still + // has the placement references from a previous pass). + aref->from_egg_quick(*bref); + combined_textures.push_back(aref); + delete bref; + + } else { + // It has changed, so drop the original and keep the new one. + combined_textures.push_back(bref); + delete aref; + } + ++ai; + ++bi; + } + } + + while (bi != new_textures.end()) { + TextureReference *bref = (*bi); + // Here's a texture reference in the new list, but not in the original + // list. Add it. + combined_textures.push_back(bref); + ++bi; + } + + while (ai != _textures.end()) { + TextureReference *aref = (*ai); + // Here's a texture reference in the original list, but not in the new + // list. Remove it. + delete aref; + ++ai; + } + + _textures.swap(combined_textures); +} + +/** + * Fills up the indicated set with the set of textures referenced by this egg + * file. It is the user's responsibility to ensure the set is empty before + * making this call; otherwise, the new textures will be appended to the + * existing set. + */ +void EggFile:: +get_textures(pset &result) const { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + result.insert((*ti)->get_texture()); + } +} + +/** + * Does some processing prior to scanning the .txa file. + */ +void EggFile:: +pre_txa_file() { + _is_surprise = true; + _first_txa_match = true; +} + +/** + * Adds the indicated set of groups, read from the .txa file, to the set of + * groups to which the egg file is assigned. + */ +void EggFile:: +match_txa_groups(const PaletteGroups &groups) { + if (_first_txa_match) { + // If this is the first line we matched in the .txa file, clear the set of + // groups we'd matched from before. We don't clear until we match a line + // in the .txa file, because if we don't match any lines we still want to + // remember what groups we used to be assigned to. + _explicitly_assigned_groups.clear(); + _first_txa_match = false; + } + + _explicitly_assigned_groups.make_union(_explicitly_assigned_groups, groups); +} + +/** + * Once the egg file has been matched against all of the matching lines the + * .txa file, do whatever adjustment is necessary. + */ +void EggFile:: +post_txa_file() { +} + +/** + * Returns the set of PaletteGroups that the egg file has been explicitly + * assigned to in the .txa file. + */ +const PaletteGroups &EggFile:: +get_explicit_groups() const { + return _explicitly_assigned_groups; +} + +/** + * Returns the PaletteGroup that was specified as the default group on the + * command line at the time the egg file last appeared on the command line. + */ +PaletteGroup *EggFile:: +get_default_group() const { + return _default_group; +} + +/** + * Returns the complete set of PaletteGroups that the egg file is assigned to. + * This is the set of all the groups it is explicitly assigned to, plus all + * the groups that these groups depend on. + */ +const PaletteGroups &EggFile:: +get_complete_groups() const { + return _complete_groups; +} + +/** + * Removes the 'surprise' flag; this file has been successfully matched + * against a line in the .txa file. + */ +void EggFile:: +clear_surprise() { + _is_surprise = false; +} + +/** + * Returns true if this particular egg file is a 'surprise', i.e. it wasn't + * matched by a line in the .txa file that didn't include the keyword 'cont'. + */ +bool EggFile:: +is_surprise() const { + return _is_surprise; +} + +/** + * Marks this particular egg file as stale, meaning that something has + * changed, such as the location of a texture within its palette, which causes + * the egg file to need to be regenerated. + */ +void EggFile:: +mark_stale() { + _is_stale = true; +} + +/** + * Returns true if the egg file needs to be updated, i.e. some palettizations + * have changed affecting it, or false otherwise. + */ +bool EggFile:: +is_stale() const { + return _is_stale; +} + +/** + * Calls TextureImage::note_egg_file() and + * SourceTextureImage::increment_egg_count() for each texture the egg file + * references, and PaletteGroup::increment_egg_count() for each palette group + * it wants. This sets up some of the back references to support determining + * an ideal texture assignment. + */ +void EggFile:: +build_cross_links() { + if (_explicitly_assigned_groups.empty()) { + // If the egg file has been assigned to no groups, we have to assign it to + // something. + _complete_groups.clear(); + _complete_groups.insert(_default_group); + _complete_groups.make_complete(_complete_groups); + + } else { + _complete_groups.make_complete(_explicitly_assigned_groups); + } + + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + TextureImage *texture = reference->get_texture(); + nassertv(texture != nullptr); + texture->note_egg_file(this); + + // Actually, this may count the same egg file multiple times for a + // particular SourceTextureImage, since a given texture may be referenced + // multiples times within an egg file. No harm done, however. + reference->get_source()->increment_egg_count(); + } + + PaletteGroups::const_iterator gi; + for (gi = _complete_groups.begin(); + gi != _complete_groups.end(); + ++gi) { + (*gi)->increment_egg_count(); + } +} + +/** + * Calls apply_properties_to_source() for each texture reference, updating all + * the referenced source textures with the complete set of property + * information from this egg file. + */ +void EggFile:: +apply_properties_to_source() { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + reference->apply_properties_to_source(); + } +} + +/** + * Once all the textures have been assigned to groups (but before they may + * actually be placed), chooses a suitable TexturePlacement for each texture + * that appears in the egg file. This will be necessary to do at some point + * before writing out the egg file anyway, and doing it before the textures + * are placed allows us to decide what the necessary UV range is for each to- + * be-placed texture. + */ +void EggFile:: +choose_placements() { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + TextureImage *texture = reference->get_texture(); + + if (reference->get_placement() != nullptr && + texture->get_groups().count(reference->get_placement()->get_group()) != 0) { + // The egg file is already using a TexturePlacement that is suitable. + // Don't bother changing it. + + } else { + // We need to select a new TexturePlacement. + PaletteGroups groups; + groups.make_intersection(get_complete_groups(), texture->get_groups()); + + // Now groups is the set of groups that the egg file requires, which + // also happen to include the texture. + + if (groups.empty()) { + // It might be empty if the egg file was assigned only to the "null" + // group (since this group is not propagated to the textures). In + // this case, choose from the wider set of groups available to the + // texture. + groups = texture->get_groups(); + } + + if (!groups.empty()) { + // It doesn't really matter which group in the set we choose, so we + // arbitrarily choose the first one. + PaletteGroup *group = (*groups.begin()); + + // Now get the TexturePlacement object that corresponds to the + // placement of this texture into this group. + TexturePlacement *placement = texture->get_placement(group); + nassertv(placement != nullptr); + + reference->set_placement(placement); + } + } + } +} + +/** + * Returns true if the EggData for this EggFile has been loaded, and not yet + * released. + */ +bool EggFile:: +has_data() const { + return (_data != nullptr); +} + +/** + * Returns true if the EggData for this EggFile has ever been loaded in this + * session. + */ +bool EggFile:: +had_data() const { + return _had_data; +} + +/** + * Once all textures have been placed appropriately, updates the egg file with + * all the information to reference the new textures. + */ +void EggFile:: +update_egg() { + nassertv(_data != nullptr); + + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + reference->update_egg(); + } +} + +/** + * Removes this egg file from all things that reference it, in preparation for + * removing it from the database. + */ +void EggFile:: +remove_egg() { + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + TexturePlacement *placement = reference->get_placement(); + placement->remove_egg(reference); + } +} + +/** + * Reads in the egg file from its _source_filename. It is only valid to call + * this if it has not already been read in, e.g. from the command line. + * Returns true if successful, false if there is an error. + * + * This may also be called after a previous call to release_egg_data(), in + * order to re-read the same egg file. + */ +bool EggFile:: +read_egg(bool noabs) { + nassertr(_data == nullptr, false); + nassertr(!_source_filename.empty(), false); + + Filename user_source_filename = + FilenameUnifier::make_user_filename(_source_filename); + + if (!_source_filename.exists()) { + nout << user_source_filename << " does not exist.\n"; + return false; + } + + PT(EggData) data = new EggData; + if (!data->read(_source_filename, user_source_filename)) { + // Failure reading. + return false; + } + + if (noabs && data->original_had_absolute_pathnames()) { + nout << _source_filename.get_basename() + << " references textures using absolute pathnames!\n"; + return false; + } + + // Extract the set of textures referenced by this egg file. + EggTextureCollection tc; + tc.find_used_textures(data); + + // Make sure each tref name is unique within a given file. + tc.uniquify_trefs(); + + // Now build up a list of new TextureReference objects that represent the + // textures actually used and their uv range, etc. + Textures new_textures; + + // We want to search for filenames based on the egg directory, and also on + // our current directory from which we originally loaded the egg file. This + // is important because it's possible the egg file referenced some textures + // or something relative to that directory. + DSearchPath dir; + dir.append_directory(_source_filename.get_dirname()); + dir.append_directory(_current_directory); + data->resolve_filenames(dir); + + // If any relative filenames remain, they are relative to the source + // directory, by convention. + data->force_filenames(_current_directory); + + if (!data->load_externals()) { + // Failure reading an external. + return false; + } + + _data = data; + _had_data = true; + remove_backstage(_data); + + // Insert a comment that shows how we first generated the egg file. + PT(EggNode) comment = new EggComment("", _egg_comment); + _data->insert(_data->begin(), comment); + + if (!_textures.empty()) { + // If we already have textures, assume we're re-reading the file. + rescan_textures(); + } + + return true; +} + +/** + * Releases the memory that was loaded by a previous call to read_egg(). + */ +void EggFile:: +release_egg_data() { + if (_data != nullptr) { + _data = nullptr; + } + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + reference->release_egg_data(); + } +} + +/** + * Writes out the egg file to its _dest_filename. Returns true if successful, + * false if there is an error. + */ +bool EggFile:: +write_egg() { + nassertr(_data != nullptr, false); + nassertr(!_dest_filename.empty(), false); + + _dest_filename.make_dir(); + nout << "Writing " << FilenameUnifier::make_user_filename(_dest_filename) + << "\n"; + if (!_data->write_egg(_dest_filename)) { + // Some error while writing. Most unusual. + _is_stale = true; + return false; + } + + _is_stale = false; + return true; +} + +/** + * Writes a one-line description of the egg file and its group assignments to + * the indicated output stream. + */ +void EggFile:: +write_description(std::ostream &out, int indent_level) const { + indent(out, indent_level) << get_name() << ": "; + if (_explicitly_assigned_groups.empty()) { + if (_default_group != nullptr) { + out << _default_group->get_name(); + } + } else { + out << _explicitly_assigned_groups; + } + + if (is_stale()) { + out << " (needs update)"; + } + out << "\n"; +} + +/** + * Writes the list of texture references to the indicated output stream, one + * per line. + */ +void EggFile:: +write_texture_refs(std::ostream &out, int indent_level) const { + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureReference *reference = (*ti); + reference->write(out, indent_level); + } +} + +/** + * Recursively walks the egg hierarchy and removes any "backstage" nodes found + * from the scene graph completely. These aren't part of the egg scene + * anyway, and removing them early helps reduce confusion. + */ +void EggFile:: +remove_backstage(EggGroupNode *node) { + EggGroupNode::iterator ci; + ci = node->begin(); + while (ci != node->end()) { + EggNode *child = (*ci); + bool remove_child = false; + + if (child->is_of_type(EggGroup::get_class_type())) { + EggGroup *egg_group; + DCAST_INTO_V(egg_group, child); + remove_child = egg_group->has_object_type("backstage"); + } + + if (remove_child) { + ci = node->erase(ci); + } else { + if (child->is_of_type(EggGroupNode::get_class_type())) { + // Recurse on children. + remove_backstage(DCAST(EggGroupNode, child)); + } + ++ci; + } + } +} + +/** + * After reloading the egg file for the second time in a given session, + * rematches the texture pointers with the TextureReference objects. + */ +void EggFile:: +rescan_textures() { + nassertv(_data != nullptr); + + // Extract the set of textures referenced by this egg file. + EggTextureCollection tc; + tc.find_used_textures(_data); + + // Make sure each tref name is unique within a given file. + tc.uniquify_trefs(); + + typedef pmap ByTRefName; + ByTRefName by_tref_name; + for (Textures::const_iterator ti = _textures.begin(); + ti != _textures.end(); + ++ti) { + TextureReference *ref = (*ti); + by_tref_name[ref->get_tref_name()] = ref; + } + + EggTextureCollection::iterator eti; + for (eti = tc.begin(); eti != tc.end(); ++eti) { + EggTexture *egg_tex = (*eti); + + ByTRefName::const_iterator tni = by_tref_name.find(egg_tex->get_name()); + if (tni == by_tref_name.end()) { + // We didn't find this TRef name last time around! + nout << _source_filename.get_basename() + << " modified during session--TRef " << egg_tex->get_name() + << " is new!\n"; + + } else { + TextureReference *ref = (*tni).second; + ref->rebind_egg_data(_data, egg_tex); + } + } +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void EggFile:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void EggFile:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_string(get_name()); + + // We don't write out _data; that needs to be reread each session. + + datagram.add_string(FilenameUnifier::make_bam_filename(_current_directory)); + datagram.add_string(FilenameUnifier::make_bam_filename(_source_filename)); + datagram.add_string(FilenameUnifier::make_bam_filename(_dest_filename)); + datagram.add_string(_egg_comment); + + datagram.add_uint32(_textures.size()); + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + writer->write_pointer(datagram, (*ti)); + } + + _explicitly_assigned_groups.write_datagram(writer, datagram); + writer->write_pointer(datagram, _default_group); + + // We don't write out _complete_groups; that is recomputed each session. + + datagram.add_bool(_is_surprise); + datagram.add_bool(_is_stale); +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int EggFile:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + + int i; + _textures.reserve(_num_textures); + for (i = 0; i < _num_textures; i++) { + TextureReference *texture; + DCAST_INTO_R(texture, p_list[pi], pi); + _textures.push_back(texture); + pi++; + } + + pi += _explicitly_assigned_groups.complete_pointers(p_list + pi, manager); + + if (p_list[pi] != nullptr) { + DCAST_INTO_R(_default_group, p_list[pi], pi); + } + pi++; + + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *EggFile:: +make_from_bam(const FactoryParams ¶ms) { + EggFile *me = new EggFile(); + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void EggFile:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + set_name(scan.get_string()); + _current_directory = FilenameUnifier::get_bam_filename(scan.get_string()); + _source_filename = FilenameUnifier::get_bam_filename(scan.get_string()); + _dest_filename = FilenameUnifier::get_bam_filename(scan.get_string()); + if (Palettizer::_read_pi_version >= 9) { + _egg_comment = scan.get_string(); + } + + _num_textures = scan.get_uint32(); + manager->read_pointers(scan, _num_textures); + + _explicitly_assigned_groups.fillin(scan, manager); + manager->read_pointer(scan); // _default_group + + _is_surprise = scan.get_bool(); + _is_stale = scan.get_bool(); + + if (Palettizer::_read_pi_version < 11) { + // If this file was written by a version of egg-palettize prior to 11, we + // didn't store the tref names on the texture references. Since we need + // that information now, it follows that every egg file is stale. + _is_stale = true; + } +} diff --git a/pandatool/src/palettizer/eggFile.h b/pandatool/src/palettizer/eggFile.h new file mode 100644 index 00000000..aa8e729e --- /dev/null +++ b/pandatool/src/palettizer/eggFile.h @@ -0,0 +1,137 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggFile.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef EGGFILE_H +#define EGGFILE_H + +#include "pandatoolbase.h" + +#include "paletteGroups.h" +#include "textureReference.h" +#include "eggData.h" +#include "filename.h" +#include "namable.h" +#include "typedWritable.h" +#include "pointerTo.h" +#include "pset.h" + +class TextureImage; + +/** + * This represents a single egg file known to the palettizer. It may + * reference a number of textures, and may also be assigned to a number of + * groups. All of its textures will try to assign themselves to one of its + * groups. + */ +class EggFile : public TypedWritable, public Namable { +public: + EggFile(); + + bool from_command_line(EggData *data, + const Filename &source_filename, + const Filename &dest_filename, + const std::string &egg_comment); + + const Filename &get_source_filename() const; + + void scan_textures(); + void get_textures(pset &result) const; + + void pre_txa_file(); + void match_txa_groups(const PaletteGroups &groups); + void post_txa_file(); + + const PaletteGroups &get_explicit_groups() const; + PaletteGroup *get_default_group() const; + const PaletteGroups &get_complete_groups() const; + void clear_surprise(); + bool is_surprise() const; + + void mark_stale(); + bool is_stale() const; + + void build_cross_links(); + void apply_properties_to_source(); + void choose_placements(); + + bool has_data() const; + bool had_data() const; + + void update_egg(); + void remove_egg(); + bool read_egg(bool noabs); + void release_egg_data(); + bool write_egg(); + + void write_description(std::ostream &out, int indent_level = 0) const; + void write_texture_refs(std::ostream &out, int indent_level = 0) const; + +private: + void remove_backstage(EggGroupNode *node); + void rescan_textures(); + +private: + PT(EggData) _data; + Filename _current_directory; + Filename _source_filename; + Filename _dest_filename; + std::string _egg_comment; + + typedef pvector Textures; + Textures _textures; + + bool _first_txa_match; + PaletteGroups _explicitly_assigned_groups; + PaletteGroup *_default_group; + PaletteGroups _complete_groups; + bool _is_surprise; + bool _is_stale; + bool _had_data; + + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // This value is only filled in while reading from the bam file; don't use + // it otherwise. + int _num_textures; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + Namable::init_type(); + register_type(_type_handle, "EggFile", + TypedWritable::get_class_type(), + Namable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/palettizer/filenameUnifier.cxx b/pandatool/src/palettizer/filenameUnifier.cxx new file mode 100644 index 00000000..f844922b --- /dev/null +++ b/pandatool/src/palettizer/filenameUnifier.cxx @@ -0,0 +1,133 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file filenameUnifier.cxx + * @author drose + * @date 2000-12-05 + */ + +#include "filenameUnifier.h" + +#include "executionEnvironment.h" + +Filename FilenameUnifier::_txa_filename; +Filename FilenameUnifier::_txa_dir; +Filename FilenameUnifier::_rel_dirname; + +FilenameUnifier::CanonicalFilenames FilenameUnifier::_canonical_filenames; + +/** + * Notes the filename the .txa file was found in. This may have come from the + * command line, or it may have been implicitly located. This has other + * implications for the FilenameUnifier, particularly in locating the bam file + * that saves the filenameUnifier state from last session. + */ +void FilenameUnifier:: +set_txa_filename(const Filename &txa_filename) { + _txa_filename = txa_filename; + _txa_dir = txa_filename.get_dirname(); + if (_txa_dir.empty()) { + _txa_dir = "."; + } + make_canonical(_txa_dir); +} + +/** + * Sets the name of the directory that texture filenames will be written + * relative to, when generating egg files. This is not the directory the + * textures are actually written to (see set_map_dirname()), but rather is the + * name of some directory above that, which will be the starting point for the + * pathnames written to the egg files. If this is empty, the full pathnames + * will be written to the egg files. + */ +void FilenameUnifier:: +set_rel_dirname(const Filename &rel_dirname) { + _rel_dirname = rel_dirname; + if (!_rel_dirname.empty()) { + make_canonical(_rel_dirname); + } +} + +/** + * Returns a new filename that's made relative to the bam file itself, + * suitable for writing to the bam file. + */ +Filename FilenameUnifier:: +make_bam_filename(Filename filename) { + make_canonical(filename); + filename.make_relative_to(_txa_dir); + return filename; +} + +/** + * Returns an absolute pathname based on the given relative pathname, + * presumably read from the bam file and relative to the bam file. + */ +Filename FilenameUnifier:: +get_bam_filename(Filename filename) { + if (!filename.empty()) { + filename.make_absolute(_txa_dir); + } + return filename; +} + +/** + * Returns a new filename that's made relative to the rel_directory, suitable + * for writing out within egg files. + */ +Filename FilenameUnifier:: +make_egg_filename(Filename filename) { + if (!filename.empty()) { + make_canonical(filename); + filename.make_relative_to(_rel_dirname); + } + return filename; +} + +/** + * Returns a new filename that's made relative to the current directory, + * suitable for reporting to the user. + */ +Filename FilenameUnifier:: +make_user_filename(Filename filename) { + if (!filename.empty()) { + make_canonical(filename); + filename.make_relative_to(ExecutionEnvironment::get_cwd()); + } + return filename; +} + +/** + * Does the same thing as Filename::make_canonical()--it converts the filename + * to its canonical form--but caches the operation so that repeated calls to + * filenames in the same directory will tend to be faster. + */ +void FilenameUnifier:: +make_canonical(Filename &filename) { + if (filename.empty()) { + return; + } + + Filename orig_dirname = filename.get_dirname(); + + CanonicalFilenames::iterator fi; + fi = _canonical_filenames.find(orig_dirname); + if (fi != _canonical_filenames.end()) { + filename.set_dirname((*fi).second); + return; + } + + Filename new_dirname = orig_dirname; + if (new_dirname.empty()) { + new_dirname = "."; + } + new_dirname.make_canonical(); + filename.set_dirname(new_dirname); + + _canonical_filenames.insert(CanonicalFilenames::value_type(orig_dirname, new_dirname)); +} diff --git a/pandatool/src/palettizer/filenameUnifier.h b/pandatool/src/palettizer/filenameUnifier.h new file mode 100644 index 00000000..0f701b3c --- /dev/null +++ b/pandatool/src/palettizer/filenameUnifier.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file filenameUnifier.h + * @author drose + * @date 2000-12-05 + */ + +#ifndef FILENAMEUNIFIER_H +#define FILENAMEUNIFIER_H + +#include "pandatoolbase.h" + +#include "filename.h" + +#include "pmap.h" + +/** + * This static class does the job of converting filenames from relative to + * absolute to canonical or whatever is appropriate. Its main purpose is to + * allow us to write relative pathnames to the bam file and turn them back + * into absolute pathnames on read, so that a given bam file does not get tied + * to absolute pathnames. + */ +class FilenameUnifier { +public: + static void set_txa_filename(const Filename &txa_filename); + static void set_rel_dirname(const Filename &rel_dirname); + + static Filename make_bam_filename(Filename filename); + static Filename get_bam_filename(Filename filename); + static Filename make_egg_filename(Filename filename); + static Filename make_user_filename(Filename filename); + static void make_canonical(Filename &filename); + +private: + + static Filename _txa_filename; + static Filename _txa_dir; + static Filename _rel_dirname; + + typedef pmap CanonicalFilenames; + static CanonicalFilenames _canonical_filenames; +}; + +#endif diff --git a/pandatool/src/palettizer/imageFile.cxx b/pandatool/src/palettizer/imageFile.cxx new file mode 100644 index 00000000..15a5ebc4 --- /dev/null +++ b/pandatool/src/palettizer/imageFile.cxx @@ -0,0 +1,480 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFile.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "imageFile.h" +#include "palettizer.h" +#include "filenameUnifier.h" +#include "paletteGroup.h" + +#include "pnmImage.h" +#include "pnmFileType.h" +#include "eggTexture.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" + +using std::string; + +TypeHandle ImageFile::_type_handle; + +/** + * + */ +ImageFile:: +ImageFile() { + _alpha_file_channel = 0; + _size_known = false; + _x_size = 0; + _y_size = 0; +} + +/** + * Sets up the ImageFile as a "shadow image" of a particular PaletteImage. + * This is a temporary ImageFile that's used to read and write the shadow + * palette image, which is used to keep a working copy of the palette. + * + * Returns true if the filename changes from what it was previously, false + * otherwise. + */ +bool ImageFile:: +make_shadow_image(const string &basename) { + bool any_changed = false; + + if (_properties._color_type != pal->_shadow_color_type || + _properties._alpha_type != pal->_shadow_alpha_type) { + + _properties._color_type = pal->_shadow_color_type; + _properties._alpha_type = pal->_shadow_alpha_type; + any_changed = true; + } + + if (set_filename(pal->_shadow_dirname, basename)) { + any_changed = true; + } + + return any_changed; +} + +/** + * Returns true if the size of the image file is known, false otherwise. + */ +bool ImageFile:: +is_size_known() const { + return _size_known; +} + +/** + * Returns the size of the image file in pixels in the X direction. It is an + * error to call this unless is_size_known() returns true. + */ +int ImageFile:: +get_x_size() const { + nassertr(is_size_known(), 0); + return _x_size; +} + +/** + * Returns the size of the image file in pixels in the Y direction. It is an + * error to call this unless is_size_known() returns true. + */ +int ImageFile:: +get_y_size() const { + nassertr(is_size_known(), 0); + return _y_size; +} + +/** + * Returns true if the number of channels in the image is known, false + * otherwise. + */ +bool ImageFile:: +has_num_channels() const { + return _properties.has_num_channels(); +} + +/** + * Returns the number of channels of the image. It is an error to call this + * unless has_num_channels() returns true. + */ +int ImageFile:: +get_num_channels() const { + return _properties.get_num_channels(); +} + +/** + * Returns the grouping properties of the image. + */ +const TextureProperties &ImageFile:: +get_properties() const { + return _properties; +} + +/** + * Resets the properties to a neutral state, for instance in preparation for + * calling update_properties() with all the known contributing properties. + */ +void ImageFile:: +clear_basic_properties() { + _properties.clear_basic(); +} + +/** + * If the indicate TextureProperties structure is more specific than this one, + * updates this one. + */ +void ImageFile:: +update_properties(const TextureProperties &properties) { + _properties.update_properties(properties); +} + +/** + * Sets the filename, and if applicable, the alpha_filename, from the + * indicated basename. The extension appropriate to the image file type + * specified in _color_type (and _alpha_type) is automatically applied. + * + * Returns true if the filename changes from what it was previously, false + * otherwise. + */ +bool ImageFile:: +set_filename(PaletteGroup *group, const string &basename) { + // Synthesize the directory name based on the map_dirname set to the + // palettizer, and the group's dirname. + string dirname; + string::iterator pi; + pi = pal->_map_dirname.begin(); + while (pi != pal->_map_dirname.end()) { + if (*pi == '%') { + ++pi; + switch (*pi) { + case '%': + dirname += '%'; + break; + + case 'g': + if (group != nullptr) { + dirname += group->get_dirname(); + } + break; + } + } else { + dirname += *pi; + } + ++pi; + } + + return set_filename(dirname, basename); +} + +/** + * Sets the filename, and if applicable, the alpha_filename, from the + * indicated basename. The extension appropriate to the image file type + * specified in _color_type (and _alpha_type) is automatically applied. + * + * Returns true if the filename changes from what it was previously, false + * otherwise. + */ +bool ImageFile:: +set_filename(const string &dirname, const string &basename) { + Filename orig_filename = _filename; + Filename orig_alpha_filename = _alpha_filename; + + _filename = Filename(dirname, basename); + _filename.standardize(); + + // Since we use set_extension() here, if the file already contains a + // filename extension it will be lost. + + // It is particularly important to note that a single embedded dot will + // appear to begin a filename extension, so if the filename does *not* + // contain an extension, but does contain an embedded dot, the filename will + // be truncated at that dot. It is therefore important that the supplied + // basename always contains either an extension or a terminating dot. + + if (_properties._color_type != nullptr) { + _filename.set_extension + (_properties._color_type->get_suggested_extension()); + } + + if (_properties._alpha_type != nullptr) { + _alpha_filename = _filename.get_fullpath_wo_extension() + "_a."; + _alpha_filename.set_extension + (_properties._alpha_type->get_suggested_extension()); + } else { + _alpha_filename = Filename(); + } + + return (_filename != orig_filename || + _alpha_filename != orig_alpha_filename); +} + +/** + * Returns the primary filename of the image file. + */ +const Filename &ImageFile:: +get_filename() const { + return _filename; +} + +/** + * Returns the alpha filename of the image file. This is the name of the file + * that contains the alpha channel, if it is stored in a separate file, or the + * empty string if it is not. + */ +const Filename &ImageFile:: +get_alpha_filename() const { + return _alpha_filename; +} + +/** + * Returns the particular channel number of the alpha image file from which + * the alpha channel should be extracted. This is normally 0 to represent the + * grayscale combination of r, g, and b; or it may be a 1-based channel number + * (for instance, 4 for the alpha channel of a 4-component image). + */ +int ImageFile:: +get_alpha_file_channel() const { + return _alpha_file_channel; +} + + +/** + * Returns true if the file or files named by the image file exist, false + * otherwise. + */ +bool ImageFile:: +exists() const { + if (!_filename.exists()) { + return false; + } + if (_properties._alpha_type != nullptr && + _properties.uses_alpha() && + !_alpha_filename.empty()) { + if (!_alpha_filename.exists()) { + return false; + } + } + + return true; +} + +/** + * Reads in the image (or images, if the alpha_filename is separate) and + * stores it in the indicated PNMImage. Returns true on success, false on + * failure. + */ +bool ImageFile:: +read(PNMImage &image) const { + nassertr(!_filename.empty(), false); + + image.set_type(_properties._color_type); + nout << "Reading " << FilenameUnifier::make_user_filename(_filename) << "\n"; + if (!image.read(_filename)) { + nout << "Unable to read.\n"; + return false; + } + + if (!_alpha_filename.empty() && _alpha_filename.exists()) { + // Read in a separate color image and an alpha channel image. + PNMImage alpha_image; + alpha_image.set_type(_properties._alpha_type); + nout << "Reading " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n"; + if (!alpha_image.read(_alpha_filename)) { + nout << "Unable to read.\n"; + return false; + } + if (image.get_x_size() != alpha_image.get_x_size() || + image.get_y_size() != alpha_image.get_y_size()) { + return false; + } + + image.add_alpha(); + + if (_alpha_file_channel == 4 || + (_alpha_file_channel == 2 && alpha_image.get_num_channels() == 2)) { + // Use the alpha channel. + for (int x = 0; x < image.get_x_size(); x++) { + for (int y = 0; y < image.get_y_size(); y++) { + image.set_alpha(x, y, alpha_image.get_alpha(x, y)); + } + } + + } else if (_alpha_file_channel >= 1 && _alpha_file_channel <= 3 && + alpha_image.get_num_channels() >= 3) { + // Use the appropriate red, green, or blue channel. + for (int x = 0; x < image.get_x_size(); x++) { + for (int y = 0; y < image.get_y_size(); y++) { + image.set_alpha(x, y, alpha_image.get_channel_val(x, y, _alpha_file_channel - 1)); + } + } + + } else { + // Use the grayscale channel. + for (int x = 0; x < image.get_x_size(); x++) { + for (int y = 0; y < image.get_y_size(); y++) { + image.set_alpha(x, y, alpha_image.get_gray(x, y)); + } + } + } + } + + return true; +} + +/** + * Writes out the image in the indicated PNMImage to the _filename and/or + * _alpha_filename. Returns true on success, false on failure. + */ +bool ImageFile:: +write(const PNMImage &image) const { + nassertr(!_filename.empty(), false); + + if (!image.has_alpha() || + _properties._alpha_type == nullptr) { + if (!_alpha_filename.empty() && _alpha_filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n"; + _alpha_filename.unlink(); + } + nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n"; + _filename.make_dir(); + if (!image.write(_filename, _properties._color_type)) { + nout << "Unable to write.\n"; + return false; + } + return true; + } + + // Write out a separate color image and an alpha channel image. + PNMImage alpha_image(image.get_x_size(), image.get_y_size(), 1, + image.get_maxval()); + for (int y = 0; y < image.get_y_size(); y++) { + for (int x = 0; x < image.get_x_size(); x++) { + alpha_image.set_gray_val(x, y, image.get_alpha_val(x, y)); + } + } + + PNMImage image_copy(image); + image_copy.remove_alpha(); + nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n"; + _filename.make_dir(); + if (!image_copy.write(_filename, _properties._color_type)) { + nout << "Unable to write.\n"; + return false; + } + + nout << "Writing " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n"; + _alpha_filename.make_dir(); + if (!alpha_image.write(_alpha_filename, _properties._alpha_type)) { + nout << "Unable to write.\n"; + return false; + } + return true; +} + +/** + * Deletes the image file or files. + */ +void ImageFile:: +unlink() { + if (!_filename.empty() && _filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(_filename) << "\n"; + _filename.unlink(); + } + if (!_alpha_filename.empty() && _alpha_filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n"; + _alpha_filename.unlink(); + } +} + +/** + * Sets the indicated EggTexture to refer to this file. + */ +void ImageFile:: +update_egg_tex(EggTexture *egg_tex) const { + nassertv(egg_tex != nullptr); + + egg_tex->set_filename(FilenameUnifier::make_egg_filename(_filename)); + + if (_properties.uses_alpha() && + !_alpha_filename.empty()) { + egg_tex->set_alpha_filename(FilenameUnifier::make_egg_filename(_alpha_filename)); + egg_tex->set_alpha_file_channel(_alpha_file_channel); + } else { + egg_tex->clear_alpha_filename(); + egg_tex->clear_alpha_file_channel(); + } + + _properties.update_egg_tex(egg_tex); +} + +/** + * Writes the filename (or pair of filenames) to the indicated output stream. + */ +void ImageFile:: +output_filename(std::ostream &out) const { + out << FilenameUnifier::make_user_filename(_filename); + if (_properties.uses_alpha() && !_alpha_filename.empty()) { + out << " " << FilenameUnifier::make_user_filename(_alpha_filename); + } +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void ImageFile:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + _properties.write_datagram(writer, datagram); + datagram.add_string(FilenameUnifier::make_bam_filename(_filename)); + datagram.add_string(FilenameUnifier::make_bam_filename(_alpha_filename)); + datagram.add_uint8(_alpha_file_channel); + datagram.add_bool(_size_known); + datagram.add_int32(_x_size); + datagram.add_int32(_y_size); +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int ImageFile:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + + pi += _properties.complete_pointers(p_list + pi, manager); + + return pi; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void ImageFile:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + _properties.fillin(scan, manager); + _filename = FilenameUnifier::get_bam_filename(scan.get_string()); + _alpha_filename = FilenameUnifier::get_bam_filename(scan.get_string()); + if (Palettizer::_read_pi_version >= 10) { + _alpha_file_channel = scan.get_uint8(); + } else { + _alpha_file_channel = 0; + } + _size_known = scan.get_bool(); + _x_size = scan.get_int32(); + _y_size = scan.get_int32(); +} diff --git a/pandatool/src/palettizer/imageFile.h b/pandatool/src/palettizer/imageFile.h new file mode 100644 index 00000000..c4d8ed90 --- /dev/null +++ b/pandatool/src/palettizer/imageFile.h @@ -0,0 +1,100 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file imageFile.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef IMAGEFILE_H +#define IMAGEFILE_H + +#include "pandatoolbase.h" + +#include "textureProperties.h" + +#include "filename.h" +#include "typedWritable.h" + +class PNMImage; +class EggTexture; +class PaletteGroup; + +/** + * This is the base class of both TextureImage and PaletteImage. It + * encapsulates all the information specific to an image file that can be + * assigned as a texture image to egg geometry. + */ +class ImageFile : public TypedWritable { +public: + ImageFile(); + + bool make_shadow_image(const std::string &basename); + + bool is_size_known() const; + int get_x_size() const; + int get_y_size() const; + bool has_num_channels() const; + int get_num_channels() const; + + const TextureProperties &get_properties() const; + void clear_basic_properties(); + void update_properties(const TextureProperties &properties); + + bool set_filename(PaletteGroup *group, const std::string &basename); + bool set_filename(const std::string &dirname, const std::string &basename); + const Filename &get_filename() const; + const Filename &get_alpha_filename() const; + int get_alpha_file_channel() const; + bool exists() const; + + bool read(PNMImage &image) const; + bool write(const PNMImage &image) const; + void unlink(); + + void update_egg_tex(EggTexture *egg_tex) const; + + void output_filename(std::ostream &out) const; + +protected: + TextureProperties _properties; + Filename _filename; + Filename _alpha_filename; + int _alpha_file_channel; + + bool _size_known; + int _x_size, _y_size; + + // The TypedWritable interface follows. +public: + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "ImageFile", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; + +}; + +#endif diff --git a/pandatool/src/palettizer/omitReason.cxx b/pandatool/src/palettizer/omitReason.cxx new file mode 100644 index 00000000..6bc14240 --- /dev/null +++ b/pandatool/src/palettizer/omitReason.cxx @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file omitReason.cxx + * @author drose + * @date 2000-12-02 + */ + +#include "omitReason.h" + +std::ostream & +operator << (std::ostream &out, OmitReason omit) { + switch (omit) { + case OR_none: + return out << "none"; + + case OR_working: + return out << "working"; + + case OR_omitted: + return out << "omitted"; + + case OR_size: + return out << "size"; + + case OR_solitary: + return out << "solitary"; + + case OR_coverage: + return out << "coverage"; + + case OR_unknown: + return out << "unknown"; + + case OR_unused: + return out << "unused"; + + case OR_default_omit: + return out << "default_omit"; + } + + return out << "**invalid**(" << (int)omit << ")"; +} diff --git a/pandatool/src/palettizer/omitReason.h b/pandatool/src/palettizer/omitReason.h new file mode 100644 index 00000000..c60a86f3 --- /dev/null +++ b/pandatool/src/palettizer/omitReason.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file omitReason.h + * @author drose + * @date 2000-11-30 + */ + +#ifndef OMITREASON_H +#define OMITREASON_H + +#include "pandatoolbase.h" + +/** + * This enumerates the reasons why a texture may not have been placed in a + * palette image. + */ +enum OmitReason { + OR_none, + // Not omitted: the texture appears on a palette image. + + OR_working, + // Still working on placing it. + + OR_omitted, + // Explicitly omitted by the user via "omit" in .txa file. + + OR_size, + // Too big to fit on a single palette image. + + OR_solitary, + // It should be placed, but it's the only one on the palette image so far, + // so there's no point. + + OR_coverage, + // The texture repeats. Specifically, the UV's for the texture exceed the + // maximum rectangle allowed by coverage_threshold. + + OR_unknown, + // The texture file cannot be read, so its size can't be determined. + + OR_unused, + // The texture is no longer used by any of the egg files that formerly + // referenced it. + + OR_default_omit, + // The texture is omitted because _omit_everything is set true. +}; + +std::ostream &operator << (std::ostream &out, OmitReason omit); + +#endif diff --git a/pandatool/src/palettizer/p3palettizer_composite1.cxx b/pandatool/src/palettizer/p3palettizer_composite1.cxx new file mode 100644 index 00000000..d5dc5d0b --- /dev/null +++ b/pandatool/src/palettizer/p3palettizer_composite1.cxx @@ -0,0 +1,24 @@ + +#include "config_palettizer.cxx" +#include "destTextureImage.cxx" +#include "eggFile.cxx" +#include "filenameUnifier.cxx" +#include "imageFile.cxx" +#include "omitReason.cxx" +#include "pal_string_utils.cxx" +#include "paletteGroup.cxx" +#include "paletteGroups.cxx" +#include "paletteImage.cxx" +#include "palettePage.cxx" +#include "palettizer.cxx" +#include "sourceTextureImage.cxx" +#include "textureImage.cxx" +#include "textureMemoryCounter.cxx" +#include "texturePlacement.cxx" +#include "texturePosition.cxx" +#include "textureProperties.cxx" +#include "textureReference.cxx" +#include "textureRequest.cxx" +#include "txaFile.cxx" +#include "txaLine.cxx" + diff --git a/pandatool/src/palettizer/pal_string_utils.cxx b/pandatool/src/palettizer/pal_string_utils.cxx new file mode 100644 index 00000000..279a7aad --- /dev/null +++ b/pandatool/src/palettizer/pal_string_utils.cxx @@ -0,0 +1,86 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pal_string_utils.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "pal_string_utils.h" + +#include "pnmFileType.h" +#include "pnmFileTypeRegistry.h" + +using std::string; + + +// Extracts the first word of the string into param, and the remainder of the +// line into value. +void +extract_param_value(const string &str, string ¶m, string &value) { + size_t i = 0; + + // First, skip all whitespace at the beginning. + while (i < str.length() && isspace(str[i])) { + i++; + } + + size_t start = i; + + // Now skip to the end of the whitespace. + while (i < str.length() && !isspace(str[i])) { + i++; + } + + size_t end = i; + + param = str.substr(start, end - start); + + // Skip a little bit further to the start of the value. + while (i < str.length() && isspace(str[i])) { + i++; + } + value = trim_right(str.substr(i)); +} + + +bool +parse_image_type_request(const string &word, PNMFileType *&color_type, + PNMFileType *&alpha_type) { + PNMFileTypeRegistry *registry = PNMFileTypeRegistry::get_global_ptr(); + color_type = nullptr; + alpha_type = nullptr; + + string color_name = word; + string alpha_name; + size_t comma = word.find(','); + if (comma != string::npos) { + // If we have a comma in the image_type, it's two types: a color type and + // an alpha type. + color_name = word.substr(0, comma); + alpha_name = word.substr(comma + 1); + } + + if (!color_name.empty()) { + color_type = registry->get_type_from_extension(color_name); + if (color_type == nullptr) { + nout << "Image file type '" << color_name << "' is unknown.\n"; + return false; + } + } + + if (!alpha_name.empty()) { + alpha_type = registry->get_type_from_extension(alpha_name); + if (alpha_type == nullptr) { + nout << "Image file type '" << alpha_name << "' is unknown.\n"; + return false; + } + } + + return true; +} diff --git a/pandatool/src/palettizer/pal_string_utils.h b/pandatool/src/palettizer/pal_string_utils.h new file mode 100644 index 00000000..4f37b330 --- /dev/null +++ b/pandatool/src/palettizer/pal_string_utils.h @@ -0,0 +1,27 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pal_string_utils.h + * @author drose + * @date 2000-11-30 + */ + +#ifndef PAL_STRING_UTILS_H +#define PAL_STRING_UTILS_H + +#include "pandatoolbase.h" +#include "string_utils.h" + +class PNMFileType; + +void extract_param_value(const std::string &str, std::string ¶m, std::string &value); + +bool parse_image_type_request(const std::string &word, PNMFileType *&color_type, + PNMFileType *&alpha_type); + +#endif diff --git a/pandatool/src/palettizer/paletteGroup.cxx b/pandatool/src/palettizer/paletteGroup.cxx new file mode 100644 index 00000000..d7e58325 --- /dev/null +++ b/pandatool/src/palettizer/paletteGroup.cxx @@ -0,0 +1,713 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteGroup.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "paletteGroup.h" +#include "palettePage.h" +#include "texturePlacement.h" +#include "textureImage.h" +#include "palettizer.h" +#include "paletteImage.h" +#include "sourceTextureImage.h" + +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "indirectCompareNames.h" +#include "pvector.h" + +using std::string; + +TypeHandle PaletteGroup::_type_handle; + +/** + * + */ +PaletteGroup:: +PaletteGroup() { + _egg_count = 0; + _dependency_level = 0; + _dependency_order = 0; + _dirname_order = 0; + _has_margin_override = false; + _margin_override = 0; +} + +/** + * Sets the directory name associated with the palette group. This is an + * optional feature that can be used to place the maps for the different + * palette groups into different install directories. + */ +void PaletteGroup:: +set_dirname(const string &dirname) { + _dirname = dirname; +} + +/** + * Returns true if the directory name has been explicitly set for this group. + * If it has not, get_dirname() returns an empty string. + */ +bool PaletteGroup:: +has_dirname() const { + return !_dirname.empty(); +} + +/** + * Returns the directory name associated with the palette group. See + * set_dirname(). + */ +const string &PaletteGroup:: +get_dirname() const { + return _dirname; +} + +/** + * Eliminates all the dependency information for this group. + */ +void PaletteGroup:: +clear_depends() { + _dependent.clear(); + _dependency_level = 0; + _dependency_order = 0; + _dirname_order = 0; +} + +/** + * Indicates a dependency of this group on some other group. This means that + * the textures assigned to this group may be considered successfully assigned + * if they are actually placed in the other group. In practice, this means + * that the textures associated with the other palette group will always be + * resident at runtime when textures from this palette group are required. + */ +void PaletteGroup:: +group_with(PaletteGroup *other) { + _dependent.insert(other); +} + +/** + * Returns the set of groups this group depends on. + */ +const PaletteGroups &PaletteGroup:: +get_groups() const { + return _dependent; +} + +/** + * Returns the set of groups this group depends on. + */ +int PaletteGroup:: +get_margin_override() const { + return _margin_override; +} + +/** + * Returns the set of groups this group depends on. + */ +void PaletteGroup:: +set_margin_override(const int override) { + _margin_override = override; + _has_margin_override = true; +} + +/** + * Returns the set of groups this group depends on. + */ +bool PaletteGroup:: +has_margin_override() const { + return _has_margin_override; +} + +/** + * Adds the set of TexturePlacements associated with this group to the + * indicated vector. The vector is not cleared before this operation; if the + * user wants to retrieve the set of placements particular to this group only, + * it is the user's responsibility to clear the vector first. + */ +void PaletteGroup:: +get_placements(pvector &placements) const { + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + placements.push_back(*pi); + } +} + +/** + * Adds the set of TexturePlacements associated with this group and all + * dependent groups to the indicated vector. See get_placements(). + */ +void PaletteGroup:: +get_complete_placements(pvector &placements) const { + PaletteGroups complete; + complete.make_complete(_dependent); + + PaletteGroups::iterator gi; + for (gi = complete.begin(); gi != complete.end(); ++gi) { + PaletteGroup *group = (*gi); + group->get_placements(placements); + } + + get_placements(placements); +} + +/** + * Unconditionally sets the dependency level and order of this group to zero, + * in preparation for a later call to set_dependency_level(). See + * set_dependency_level(). + */ +void PaletteGroup:: +reset_dependency_level() { + _dependency_level = 0; + _dependency_order = 0; + _dirname_order = 0; +} + +/** + * Sets the dependency level of this group to the indicated level, provided + * that level is not lower than the level that was set previously. Also + * cascades to all dependent groups. See get_dependency_level(). + * + * This call recurses to correctly set the dependency level of all + * PaletteGroups in the hierarchy. + */ +void PaletteGroup:: +set_dependency_level(int level) { + if (level > _dependency_level) { + _dependency_level = level; + PaletteGroups::iterator gi; + for (gi = _dependent.begin(); gi != _dependent.end(); ++gi) { + PaletteGroup *group = (*gi); + group->set_dependency_level(level + 1); + } + } +} + +/** + * Updates the dependency order of this group. This number is the inverse of + * the dependency level, and can be used to rank the groups in order so that + * all the groups that a given group depends on will appear first in the list. + * See get_dependency_order(). + * + * This function returns true if anything was changed, false otherwise. + */ +bool PaletteGroup:: +set_dependency_order() { + bool any_changed = false; + + PaletteGroups::iterator gi; + for (gi = _dependent.begin(); gi != _dependent.end(); ++gi) { + PaletteGroup *group = (*gi); + if (group->set_dependency_order()) { + any_changed = true; + } + + if (_dependency_order <= group->get_dependency_order()) { + _dependency_order = group->get_dependency_order() + 1; + any_changed = true; + } + + if (_dirname == group->get_dirname()) { + // The dirname orders should be equal. + if (_dirname_order < group->get_dirname_order()) { + _dirname_order = group->get_dirname_order(); + any_changed = true; + } + } else { + // The dirname orders should be different. + if (_dirname_order <= group->get_dirname_order()) { + _dirname_order = group->get_dirname_order() + 1; + any_changed = true; + } + } + } + + return any_changed; +} + +/** + * Returns the dependency level of this group. This is a measure of how + * specific the group is; the lower the dependency level, the more specific + * the group. + * + * Groups depend on other groups in a hierarchical relationship. In general, + * if group a depends on group b, then b->get_dependency_level() > + * a->get_dependency_level(). + * + * Thus, groups that lots of other groups depend on have a higher dependency + * level; groups that no one else depends on have a low dependency level. + * This is important when deciding which groups are best suited for assigning + * a texture to; in general, the texture should be assigned to the most + * specific suitable group (i.e. the one with the lowest dependency level). + */ +int PaletteGroup:: +get_dependency_level() const { + return _dependency_level; +} + +/** + * Returns the dependency order of this group. This is similar in principle + * to the dependency level, but it represents the inverse concept: if group a + * depends on group b, then a->get_dependency_order() > + * b->get_dependency_order(). + * + * This is not exactly the same thing as n - get_dependency_level(). In + * particular, this can be used to sort the groups into an ordering such that + * all the groups that group a depends on appear before group a in the list. + */ +int PaletteGroup:: +get_dependency_order() const { + return _dependency_order; +} + +/** + * Returns the dependency order of this group. This is similar in principle + * to the dependency level, but it represents the inverse concept: if group a + * depends on group b, then a->get_dirname_order() > b->get_dirname_order(). + * + * This is not exactly the same thing as n - get_dependency_level(). In + * particular, this can be used to sort the groups into an ordering such that + * all the groups that group a depends on appear before group a in the list. + */ +int PaletteGroup:: +get_dirname_order() const { + return _dirname_order; +} + +/** + * Returns true if this group should be preferred for adding textures over the + * other group, if both are available. In other words, this is a more + * specific group than the other one. + */ +bool PaletteGroup:: +is_preferred_over(const PaletteGroup &other) const { + if (get_dirname_order() != other.get_dirname_order()) { + return (get_dirname_order() > other.get_dirname_order()); + + } else if (get_dependency_order() != other.get_dependency_order()) { + return (get_dependency_order() > other.get_dependency_order()); + + } else { + return (get_egg_count() < other.get_egg_count()); + } +} + +/** + * Increments by one the number of egg files that are known to reference this + * PaletteGroup. This is designed to aid the heuristics in texture placing; + * it's useful to know how many different egg files are sharing a particular + * PaletteGroup. + */ +void PaletteGroup:: +increment_egg_count() { + _egg_count++; +} + +/** + * Returns the number of egg files that share this PaletteGroup. + */ +int PaletteGroup:: +get_egg_count() const { + return _egg_count; +} + +/** + * Returns the page associated with the indicated properties. If no page + * object has yet been created, creates one. + */ +PalettePage *PaletteGroup:: +get_page(const TextureProperties &properties) { + Pages::iterator pi = _pages.find(properties); + if (pi != _pages.end()) { + return (*pi).second; + } + + PalettePage *page = new PalettePage(this, properties); + bool inserted = _pages.insert(Pages::value_type(properties, page)).second; + nassertr(inserted, page); + return page; +} + +/** + * Marks the indicated Texture as ready for placing somewhere within this + * group, and returns a placeholder TexturePlacement object. The texture is + * not placed immediately, but may be placed later when place_all() is called; + * at this time, the TexturePlacement fields will be filled in as appropriate. + */ +TexturePlacement *PaletteGroup:: +prepare(TextureImage *texture) { + TexturePlacement *placement = new TexturePlacement(texture, this); + _placements.insert(placement); + + // [gjeon] update swapTexture information + TextureSwapInfo::iterator tsi = _textureSwapInfo.find(texture->get_name()); + if (tsi != _textureSwapInfo.end()) { + vector_string swapTextures = (*tsi).second; + + vector_string::const_iterator wi; + wi = swapTextures.begin(); + ++wi; + ++wi; + + // [gjeon] since swapped texture usually didn't mapped to any egg file we + // need to create soucreTextureImage by using original texture file's info + const string originalTextureName = (*wi); + TextureImage *originalTexture = pal->get_texture(originalTextureName); + SourceTextureImage *source = originalTexture->get_preferred_source(); + const Filename originalTextureFilename = source->get_filename(); + const Filename originalTextureAlphaFilename = source->get_alpha_filename(); + int originalTextureAlphaFileChannel = source->get_alpha_file_channel(); + + ++wi; + while (wi != swapTextures.end()) { + const string &swapTextureName = (*wi); + TextureImage *swapTextureImage = pal->get_texture(swapTextureName); + Filename swapTextureFilename = Filename(originalTextureFilename.get_dirname(), swapTextureName + "." + originalTextureFilename.get_extension()); + swapTextureImage->get_source(swapTextureFilename, originalTextureAlphaFilename, originalTextureAlphaFileChannel); + placement->_textureSwaps.push_back(swapTextureImage); + ++wi; + } + } + + return placement; +} + +/** + * Removes the texture from its position on a PaletteImage, if it has been so + * placed. + */ +void PaletteGroup:: +unplace(TexturePlacement *placement) { + nassertv(placement->get_group() == this); + + Placements::iterator pi; + pi = _placements.find(placement); + if (pi != _placements.end()) { + _placements.erase(pi); + + if (placement->is_placed()) { + placement->get_page()->unplace(placement); + } + } +} + +/** + * Once all the textures have been assigned to this group, try to place them + * all onto suitable PaletteImages. + */ +void PaletteGroup:: +place_all() { + // First, go through our prepared textures and assign each unplaced one to + // an appropriate page. + Placements::iterator pli; + for (pli = _placements.begin(); pli != _placements.end(); ++pli) { + TexturePlacement *placement = (*pli); + + if (placement->get_omit_reason() == OR_working) { + PalettePage *page = get_page(placement->get_properties()); + page->assign(placement); + } + } + + // Then, go through the pages and actually do the placing. + Pages::iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->place_all(); + } +} + +/** + * Checks for new information on any textures within the group for which some + * of the saved information is incomplete. This may be necessary before we + * can properly place all of the textures. + */ +void PaletteGroup:: +update_unknown_textures(const TxaFile &txa_file) { + Placements::iterator pli; + for (pli = _placements.begin(); pli != _placements.end(); ++pli) { + TexturePlacement *placement = (*pli); + + if (!placement->is_size_known()) { + // This texture's size isn't known; we have to determine its size. + TextureImage *texture = placement->get_texture(); + if (!texture->got_txa_file()) { + // But first, we need to look up the texture in the .txa file. + texture->pre_txa_file(); + txa_file.match_texture(texture); + texture->post_txa_file(); + } + + placement->determine_size(); + } + } +} + +/** + * Writes a list of the PaletteImages associated with this group, and all of + * their textures, to the indicated output stream. + */ +void PaletteGroup:: +write_image_info(std::ostream &out, int indent_level) const { + Pages::const_iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->write_image_info(out, indent_level); + } + + // Write out all the unplaced textures, in alphabetical order by name. + pvector placement_vector; + placement_vector.reserve(_placements.size()); + Placements::const_iterator pli; + for (pli = _placements.begin(); pli != _placements.end(); ++pli) { + TexturePlacement *placement = (*pli); + if (placement->get_omit_reason() != OR_none) { + placement_vector.push_back(placement); + } + } + sort(placement_vector.begin(), placement_vector.end(), + IndirectCompareNames()); + + pvector::const_iterator pvi; + for (pvi = placement_vector.begin(); + pvi != placement_vector.end(); + ++pvi) { + TexturePlacement *placement = (*pvi); + + indent(out, indent_level) + << placement->get_texture()->get_name() + << " unplaced because "; + switch (placement->get_omit_reason()) { + case OR_coverage: + out << "coverage (" << placement->get_uv_area() << ")"; + break; + + case OR_size: + out << "size (" << placement->get_x_size() << " " + << placement->get_y_size() << ")"; + break; + + default: + out << placement->get_omit_reason(); + } + out << "\n"; + } +} + +/** + * Attempts to resize each PalettteImage down to its smallest possible size. + */ +void PaletteGroup:: +optimal_resize() { + Pages::iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->optimal_resize(); + } +} + +/** + * Throws away all of the current PaletteImages, so that new ones may be + * created (and the packing made more optimal). + */ +void PaletteGroup:: +reset_images() { + Pages::iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->reset_images(); + } +} + +/** + * Ensures that each PaletteImage's _shadow_image has the correct filename and + * image types, based on what was supplied on the command line and in the .txa + * file. + */ +void PaletteGroup:: +setup_shadow_images() { + Pages::iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->setup_shadow_images(); + } +} + +/** + * Regenerates each PaletteImage on this group that needs it. + */ +void PaletteGroup:: +update_images(bool redo_all) { + Pages::iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + PalettePage *page = (*pai).second; + page->update_images(redo_all); + } +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void PaletteGroup:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void PaletteGroup:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_string(get_name()); + datagram.add_string(_dirname); + _dependent.write_datagram(writer, datagram); + + datagram.add_int32(_dependency_level); + datagram.add_int32(_dependency_order); + datagram.add_int32(_dirname_order); + + datagram.add_uint32(_placements.size()); + Placements::const_iterator pli; + for (pli = _placements.begin(); pli != _placements.end(); ++pli) { + writer->write_pointer(datagram, (*pli)); + } + + datagram.add_uint32(_pages.size()); + Pages::const_iterator pai; + for (pai = _pages.begin(); pai != _pages.end(); ++pai) { + writer->write_pointer(datagram, (*pai).second); + } + datagram.add_bool(_has_margin_override); + datagram.add_int16(_margin_override); + +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int PaletteGroup:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + + pi += _dependent.complete_pointers(p_list + pi, manager); + + int i; + for (i = 0; i < _num_placements; i++) { + TexturePlacement *placement; + DCAST_INTO_R(placement, p_list[pi++], pi); + bool inserted = _placements.insert(placement).second; + nassertr(inserted, pi); + } + + // We must store the list of pages in a temporary vector first. We can't + // put them directly into the map because the map requires that all the + // pointers in the page's get_properties() member have been filled in, which + // may not have happened yet. + _load_pages.reserve(_num_pages); + for (i = 0; i < _num_pages; i++) { + PalettePage *page; + DCAST_INTO_R(page, p_list[pi++], pi); + _load_pages.push_back(page); + } + + return pi; +} + +/** + * This method is called by the BamReader after all pointers everywhere in the + * world have been completely read in. It's a hook at which the object can do + * whatever final setup it requires that depends on other pointers being + * valid. + */ +void PaletteGroup:: +finalize(BamReader *) { + // Now we can copy the pages into the actual map. + pvector::const_iterator pi; + for (pi = _load_pages.begin(); pi != _load_pages.end(); ++pi) { + PalettePage *page = (*pi); + bool inserted = _pages. + insert(Pages::value_type(page->get_properties(), page)).second; + nassertv(inserted); + } + + _load_pages.clear(); +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *PaletteGroup:: +make_from_bam(const FactoryParams ¶ms) { + PaletteGroup *me = new PaletteGroup; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + manager->register_finalize(me); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void PaletteGroup:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + set_name(scan.get_string()); + _dirname = scan.get_string(); + _dependent.fillin(scan, manager); + + _dependency_level = scan.get_int32(); + _dependency_order = scan.get_int32(); + _dirname_order = scan.get_int32(); + + _num_placements = scan.get_uint32(); + manager->read_pointers(scan, _num_placements); + + _num_pages = scan.get_uint32(); + manager->read_pointers(scan, _num_pages); + + if(Palettizer::_read_pi_version >= 19) { + _has_margin_override = scan.get_bool(); + _margin_override = scan.get_int16(); + } +} + +/** + * Store textureswap information from textures.txa + */ +void PaletteGroup:: +add_texture_swap_info(const string sourceTextureName, const vector_string &swapTextures) { + TextureSwapInfo::iterator tsi = _textureSwapInfo.find(sourceTextureName); + if (tsi != _textureSwapInfo.end()) { + _textureSwapInfo.erase(tsi); + } + _textureSwapInfo.insert(TextureSwapInfo::value_type(sourceTextureName, swapTextures)); +} + +/** + * Returns textureswap information is set or not, True if it's not set. + */ +bool PaletteGroup:: +is_none_texture_swap() const { + return _textureSwapInfo.empty(); +} diff --git a/pandatool/src/palettizer/paletteGroup.h b/pandatool/src/palettizer/paletteGroup.h new file mode 100644 index 00000000..5c20e1ca --- /dev/null +++ b/pandatool/src/palettizer/paletteGroup.h @@ -0,0 +1,151 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteGroup.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef PALETTEGROUP_H +#define PALETTEGROUP_H + +#include "pandatoolbase.h" + +#include "paletteGroups.h" +#include "textureProperties.h" + +#include "namable.h" +#include "typedWritable.h" + +#include "pset.h" +#include "pvector.h" +#include "vector_string.h" + +class EggFile; +class TexturePlacement; +class PalettePage; +class TextureImage; +class TxaFile; + +/** + * This is the highest level of grouping for TextureImages. Textures are + * assigned to one or several PaletteGroups based on the information in the + * .txa file; each PaletteGroup is conceptually a collection of textures that + * are to be moved around (into texture memory, downloaded, etc.) in one big + * chunk. It is the set of all textures that may be displayed together at any + * given time. + */ +class PaletteGroup : public TypedWritable, public Namable { +public: + PaletteGroup(); + + void set_dirname(const std::string &dirname); + bool has_dirname() const; + const std::string &get_dirname() const; + + void clear_depends(); + void group_with(PaletteGroup *other); + const PaletteGroups &get_groups() const; + + void get_placements(pvector &placements) const; + void get_complete_placements(pvector &placements) const; + + void reset_dependency_level(); + void set_dependency_level(int level); + bool set_dependency_order(); + int get_dependency_level() const; + int get_dependency_order() const; + int get_dirname_order() const; + + void set_margin_override(const int override); + int get_margin_override() const; + bool has_margin_override() const; + + bool is_preferred_over(const PaletteGroup &other) const; + + void increment_egg_count(); + int get_egg_count() const; + + PalettePage *get_page(const TextureProperties &properties); + + TexturePlacement *prepare(TextureImage *texture); + + void unplace(TexturePlacement *placement); + + void place_all(); + void update_unknown_textures(const TxaFile &txa_file); + + void write_image_info(std::ostream &out, int indent_level = 0) const; + void optimal_resize(); + void reset_images(); + void setup_shadow_images(); + void update_images(bool redo_all); + + void add_texture_swap_info(const std::string sourceTextureName, const vector_string &swapTextures); + bool is_none_texture_swap() const; + +private: + std::string _dirname; + int _egg_count; + PaletteGroups _dependent; + int _dependency_level; + int _dependency_order; + int _dirname_order; + + typedef pset Placements; + Placements _placements; + + typedef pmap Pages; + Pages _pages; + + typedef pmap TextureSwapInfo; + TextureSwapInfo _textureSwapInfo; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + virtual void finalize(BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // These values are only filled in while reading from the bam file; don't + // use them otherwise. + int _num_placements; + int _num_pages; + bool _has_margin_override; + int _margin_override; + pvector _load_pages; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + Namable::init_type(); + register_type(_type_handle, "PaletteGroup", + TypedWritable::get_class_type(), + Namable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; + + friend class PaletteGroups; +}; + +#endif diff --git a/pandatool/src/palettizer/paletteGroups.cxx b/pandatool/src/palettizer/paletteGroups.cxx new file mode 100644 index 00000000..ada0702a --- /dev/null +++ b/pandatool/src/palettizer/paletteGroups.cxx @@ -0,0 +1,343 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteGroups.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "paletteGroups.h" +#include "paletteGroup.h" + +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "indirectCompareNames.h" +#include "pvector.h" + +TypeHandle PaletteGroups::_type_handle; + +/** + * + */ +PaletteGroups:: +PaletteGroups() { +} + +/** + * + */ +PaletteGroups:: +PaletteGroups(const PaletteGroups ©) : + _groups(copy._groups) +{ +} + +/** + * + */ +void PaletteGroups:: +operator = (const PaletteGroups ©) { + _groups = copy._groups; +} + +/** + * Inserts a new group to the set, if it is not already there. + */ +void PaletteGroups:: +insert(PaletteGroup *group) { + _groups.insert(group); +} + +/** + * Returns the number of times the given group appears in the set. This is + * either 1 if it appears at all, or 0 if it does not appear. + */ +PaletteGroups::size_type PaletteGroups:: +count(PaletteGroup *group) const { + return _groups.count(group); +} + +/** + * Completes the set with the transitive closure of all dependencies: for each + * PaletteGroup already in the set a, all of the groups that it depends on are + * added to the set, and so on. The indicated set a may be the same as this + * set. + */ +void PaletteGroups:: +make_complete(const PaletteGroups &a) { + Groups result; + + Groups::const_iterator gi; + for (gi = a._groups.begin(); gi != a._groups.end(); ++gi) { + r_make_complete(result, *gi); + } + + _groups.swap(result); +} + +/** + * Computes the union of PaletteGroups a and b, and stores the result in this + * object. The result may be the same object as either a or b. + */ +void PaletteGroups:: +make_union(const PaletteGroups &a, const PaletteGroups &b) { + Groups u; + + Groups::const_iterator ai, bi; + ai = a._groups.begin(); + bi = b._groups.begin(); + + while (ai != a._groups.end() && bi != b._groups.end()) { + if ((*ai) < (*bi)) { + u.insert(u.end(), *ai); + ++ai; + + } else if ((*bi) < (*ai)) { + u.insert(u.end(), *bi); + ++bi; + + } else { // (*ai) == (*bi) + u.insert(u.end(), *ai); + ++ai; + ++bi; + } + } + + while (ai != a._groups.end()) { + u.insert(u.end(), *ai); + ++ai; + } + + while (bi != b._groups.end()) { + u.insert(u.end(), *bi); + ++bi; + } + + _groups.swap(u); +} + +/** + * Computes the intersection of PaletteGroups a and b, and stores the result + * in this object. The result may be the same object as either a or b. + */ +void PaletteGroups:: +make_intersection(const PaletteGroups &a, const PaletteGroups &b) { + Groups i; + + Groups::const_iterator ai, bi; + ai = a._groups.begin(); + bi = b._groups.begin(); + + while (ai != a._groups.end() && bi != b._groups.end()) { + if ((*ai) < (*bi)) { + ++ai; + + } else if ((*bi) < (*ai)) { + ++bi; + + } else { // (*ai) == (*bi) + i.insert(i.end(), *ai); + ++ai; + ++bi; + } + } + + _groups.swap(i); +} + +/** + * Removes the special "null" group from the set. This is a special group + * that egg files may be assigned to, but which textures never are; it + * indicates that the egg file should not influence the palette assignment. + */ +void PaletteGroups:: +remove_null() { + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + if ((*gi)->get_name() == "null") { + _groups.erase(gi); + return; + } + } +} + +/** + * Empties the set. + */ +void PaletteGroups:: +clear() { + _groups.clear(); +} + +/** + * Returns true if the set is empty, false otherwise. + */ +bool PaletteGroups:: +empty() const { + return _groups.empty(); +} + +/** + * Returns the number of elements in the set. + */ +PaletteGroups::size_type PaletteGroups:: +size() const { + return _groups.size(); +} + +/** + * Returns an iterator suitable for traversing the set. + */ +PaletteGroups::iterator PaletteGroups:: +begin() const { + return _groups.begin(); +} + +/** + * Returns an iterator suitable for traversing the set. + */ +PaletteGroups::iterator PaletteGroups:: +end() const { + return _groups.end(); +} + +/** + * + */ +void PaletteGroups:: +output(std::ostream &out) const { + if (!_groups.empty()) { + // Sort the group names into order by name for output. + pvector group_vector; + group_vector.reserve(_groups.size()); + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + group_vector.push_back(*gi); + } + sort(group_vector.begin(), group_vector.end(), + IndirectCompareNames()); + + pvector::const_iterator gvi = group_vector.begin(); + out << (*gvi)->get_name(); + ++gvi; + while (gvi != group_vector.end()) { + out << " " << (*gvi)->get_name(); + ++gvi; + } + } +} + +/** + * + */ +void PaletteGroups:: +write(std::ostream &out, int indent_level) const { + // Sort the group names into order by name for output. + pvector group_vector; + group_vector.reserve(_groups.size()); + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + group_vector.push_back(*gi); + } + sort(group_vector.begin(), group_vector.end(), + IndirectCompareNames()); + + pvector::const_iterator gvi; + for (gvi = group_vector.begin(); gvi != group_vector.end(); ++gvi) { + indent(out, indent_level) << (*gvi)->get_name() << "\n"; + } +} + +/** + * The recursive implementation of make_complete(), this adds the indicated + * group and all of its dependencies to the set. + */ +void PaletteGroups:: +r_make_complete(PaletteGroups::Groups &result, PaletteGroup *group) { + bool inserted = result.insert(group).second; + + if (inserted) { + Groups::const_iterator gi; + for (gi = group->_dependent._groups.begin(); + gi != group->_dependent._groups.end(); + ++gi) { + r_make_complete(result, *gi); + } + } +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void PaletteGroups:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void PaletteGroups:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_uint32(_groups.size()); + + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + writer->write_pointer(datagram, *gi); + } +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int PaletteGroups:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + for (int i = 0; i < _num_groups; i++) { + PaletteGroup *group; + DCAST_INTO_R(group, p_list[pi++], i); + _groups.insert(group); + } + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *PaletteGroups:: +make_from_bam(const FactoryParams ¶ms) { + PaletteGroups *me = new PaletteGroups; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void PaletteGroups:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + _num_groups = scan.get_int32(); + manager->read_pointers(scan, _num_groups); +} diff --git a/pandatool/src/palettizer/paletteGroups.h b/pandatool/src/palettizer/paletteGroups.h new file mode 100644 index 00000000..b9858f82 --- /dev/null +++ b/pandatool/src/palettizer/paletteGroups.h @@ -0,0 +1,111 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteGroups.h + * @author drose + * @date 2000-11-30 + */ + +#ifndef PALETTEGROUPS_H +#define PALETTEGROUPS_H + +#include "pandatoolbase.h" +#include "typedWritable.h" +#include "pset.h" + +class PaletteGroup; +class FactoryParams; + +/** + * A set of PaletteGroups. This presents an interface very like an STL set, + * with a few additional functions. + */ +class PaletteGroups : public TypedWritable { +private: + typedef pset Groups; + +public: +#ifndef _WIN32 + typedef Groups::const_pointer pointer; + typedef Groups::const_pointer const_pointer; +#endif + typedef Groups::const_reference reference; + typedef Groups::const_reference const_reference; + typedef Groups::const_iterator iterator; + typedef Groups::const_iterator const_iterator; + typedef Groups::const_reverse_iterator reverse_iterator; + typedef Groups::const_reverse_iterator const_reverse_iterator; + typedef Groups::size_type size_type; + typedef Groups::difference_type difference_type; + + PaletteGroups(); + PaletteGroups(const PaletteGroups ©); + void operator =(const PaletteGroups ©); + + void insert(PaletteGroup *group); + size_type count(PaletteGroup *group) const; + void make_complete(const PaletteGroups &a); + void make_union(const PaletteGroups &a, const PaletteGroups &b); + void make_intersection(const PaletteGroups &a, const PaletteGroups &b); + void remove_null(); + void clear(); + + bool empty() const; + size_type size() const; + iterator begin() const; + iterator end() const; + + void output(std::ostream &out) const; + void write(std::ostream &out, int indent_level = 0) const; + +private: + void r_make_complete(Groups &result, PaletteGroup *group); + + Groups _groups; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + +public: + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // This value is only filled in while reading from the bam file; don't use + // it otherwise. + int _num_groups; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "PaletteGroups", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream &operator << (std::ostream &out, const PaletteGroups &groups) { + groups.output(out); + return out; +} + +#endif diff --git a/pandatool/src/palettizer/paletteImage.cxx b/pandatool/src/palettizer/paletteImage.cxx new file mode 100644 index 00000000..3ceedbf5 --- /dev/null +++ b/pandatool/src/palettizer/paletteImage.cxx @@ -0,0 +1,1073 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteImage.cxx + * @author drose + * @date 2000-12-01 + */ + +#include "paletteImage.h" +#include "palettePage.h" +#include "paletteGroup.h" +#include "texturePlacement.h" +#include "palettizer.h" +#include "textureImage.h" +#include "sourceTextureImage.h" +#include "filenameUnifier.h" + +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "string_utils.h" + +#include + +TypeHandle PaletteImage::_type_handle; + +/** + * The default constructor is only for the convenience of the bam reader. + */ +PaletteImage::ClearedRegion:: +ClearedRegion() { + _x = 0; + _y = 0; + _x_size = 0; + _y_size = 0; +} + +/** + * + */ +PaletteImage::ClearedRegion:: +ClearedRegion(TexturePlacement *placement) { + _x = placement->get_placed_x(); + _y = placement->get_placed_y(); + _x_size = placement->get_placed_x_size(); + _y_size = placement->get_placed_y_size(); +} + +/** + * + */ +PaletteImage::ClearedRegion:: +ClearedRegion(const PaletteImage::ClearedRegion ©) : + _x(copy._x), + _y(copy._y), + _x_size(copy._x_size), + _y_size(copy._y_size) +{ +} + +/** + * + */ +void PaletteImage::ClearedRegion:: +operator = (const PaletteImage::ClearedRegion ©) { + _x = copy._x; + _y = copy._y; + _x_size = copy._x_size; + _y_size = copy._y_size; +} + +/** + * Sets the appropriate region of the image to black. + */ +void PaletteImage::ClearedRegion:: +clear(PNMImage &image) { + LRGBColorf rgb(pal->_background[0], pal->_background[1], pal->_background[2]); + float alpha = pal->_background[3]; + + for (int y = _y; y < _y + _y_size; y++) { + for (int x = _x; x < _x + _x_size; x++) { + image.set_xel(x, y, rgb); + } + } + if (image.has_alpha()) { + for (int y = _y; y < _y + _y_size; y++) { + for (int x = _x; x < _x + _x_size; x++) { + image.set_alpha(x, y, alpha); + } + } + } +} + +/** + * Writes the contents of the ClearedRegion to the indicated datagram. + */ +void PaletteImage::ClearedRegion:: +write_datagram(Datagram &datagram) const { + datagram.add_int32(_x); + datagram.add_int32(_y); + datagram.add_int32(_x_size); + datagram.add_int32(_y_size); +} + +/** + * Extracts the contents of the ClearedRegion from the indicated datagram. + */ +void PaletteImage::ClearedRegion:: +fillin(DatagramIterator &scan) { + _x = scan.get_int32(); + _y = scan.get_int32(); + _x_size = scan.get_int32(); + _y_size = scan.get_int32(); +} + + + + + + +/** + * The default constructor is only for the convenience of the Bam reader. + */ +PaletteImage:: +PaletteImage() { + _page = nullptr; + _index = 0; + _new_image = false; + _got_image = false; + + _swapped_image = 0; +} + +/** + * + */ +PaletteImage:: +PaletteImage(PalettePage *page, int index) : + _page(page), + _index(index) +{ + _properties = page->get_properties(); + _size_known = true; + _x_size = pal->_pal_x_size; + _y_size = pal->_pal_y_size; + _new_image = true; + _got_image = false; + _swapped_image = 0; + + setup_filename(); +} + +/** + * + */ +PaletteImage:: +PaletteImage(PalettePage *page, int index, unsigned swapIndex) : + _page(page), + _index(index), + _swapped_image(swapIndex) +{ + _properties = page->get_properties(); + _size_known = true; + _x_size = pal->_pal_x_size; + _y_size = pal->_pal_y_size; + _new_image = true; + _got_image = false; + + setup_filename(); +} + + +/** + * Returns the particular PalettePage this image is associated with. + */ +PalettePage *PaletteImage:: +get_page() const { + return _page; +} + +/** + * Returns true if there are no textures, or only one "solitary" texture, + * placed on the image. In either case, the PaletteImage need not be + * generated. + */ +bool PaletteImage:: +is_empty() const { + if (_placements.empty()) { + // The image is genuinely empty. + return true; + + } else if (_placements.size() == 1) { + // If the image has exactly one texture, we consider the image empty only + // if the texture is actually flagged as 'solitary'. + return (_placements[0]->get_omit_reason() == OR_solitary); + + } else { + // The image has more than one texture, so it's definitely not empty. + return false; + } +} + +/** + * Returns the fraction of the PaletteImage that is actually used by any + * textures. This is 1.0 if every pixel in the PaletteImage is used, or 0.0 + * if none are. Normally it will be somewhere in between. + */ +double PaletteImage:: +count_utilization() const { + int used_pixels = 0; + + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + + int texture_pixels = + placement->get_placed_x_size() * + placement->get_placed_y_size(); + used_pixels += texture_pixels; + } + + int total_pixels = get_x_size() * get_y_size(); + + return (double)used_pixels / (double)total_pixels; +} + +/** + * Returns the a weighted average of the fraction of coverage represented by + * all of the textures placed on the palette. This number represents the + * fraction of wasted pixels in the palette image consumed by copying the same + * pixels multiple times into the palette, or if the number is negative, it + * represents the fraction of pixels saved by not having to copy the entire + * texture into the palette. + */ +double PaletteImage:: +count_coverage() const { + int coverage_pixels = 0; + + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + TextureImage *texture = placement->get_texture(); + nassertr(texture != nullptr, 0.0); + + int orig_pixels = + texture->get_x_size() * + texture->get_y_size(); + int placed_pixels = + placement->get_placed_x_size() * + placement->get_placed_y_size(); + + coverage_pixels += placed_pixels - orig_pixels; + } + + int total_pixels = get_x_size() * get_y_size(); + + return (double)coverage_pixels / (double)total_pixels; +} + +/** + * Attempts to place the indicated texture on the image. Returns true if + * successful, or false if there was no available space. + */ +bool PaletteImage:: +place(TexturePlacement *placement) { + nassertr(placement->is_size_known(), true); + nassertr(!placement->is_placed(), true); + + int x, y; + if (find_hole(x, y, placement->get_x_size(), placement->get_y_size())) { + placement->place_at(this, x, y); + _placements.push_back(placement); + + // [gjeon] create swappedImages + TexturePlacement::TextureSwaps::iterator tsi; + for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) { + if ((tsi - placement->_textureSwaps.begin()) >= (int)_swappedImages.size()) { + PaletteImage *swappedImage = new PaletteImage(_page, _swappedImages.size(), tsi - placement->_textureSwaps.begin() + 1); + swappedImage->_masterPlacements = &_placements; + _swappedImages.push_back(swappedImage); + } + } + + return true; + } + + return false; +} + +/** + * Removes the texture from the image. + */ +void PaletteImage:: +unplace(TexturePlacement *placement) { + nassertv(placement->is_placed() && placement->get_image() == this); + + Placements::iterator pi; + pi = find(_placements.begin(), _placements.end(), placement); + while (pi != _placements.end()) { + _placements.erase(pi); + pi = find(_placements.begin(), _placements.end(), placement); + } + _cleared_regions.push_back(ClearedRegion(placement)); +} + +/** + * To be called after all textures have been placed on the image, this checks + * to see if there is only one texture on the image. If there is, it is + * flagged as 'solitary' so that the egg files will not needlessly reference + * the palettized image. + * + * However, if pal->_omit_solitary is false, we generally don't change + * textures to solitary state. + */ +void PaletteImage:: +check_solitary() { + if (_placements.size() == 1) { + // How sad, only one. + TexturePlacement *placement = *_placements.begin(); + nassertv(placement->get_omit_reason() == OR_none || + placement->get_omit_reason() == OR_solitary); + + if (pal->_omit_solitary || placement->get_omit_reason() == OR_solitary) { + // We only omit the solitary texture if (a) we have omit_solitary in + // effect, or (b) we don't have omit_solitary in effect now, but we did + // before, and the texture is still flagged as solitary from that + // previous pass. + placement->omit_solitary(); + } + + } else { + // Zero or multiple. + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + /* + if (!(placement->get_omit_reason() == OR_none || + placement->get_omit_reason() == OR_solitary)) { + nout << "texture " << *placement->get_texture() << " is omitted for " + << placement->get_omit_reason() << "\n"; + } + */ + nassertv(placement->get_omit_reason() == OR_none || + placement->get_omit_reason() == OR_solitary); + placement->not_solitary(); + } + } +} + +/** + * Attempts to resize the palette image to as small as it can go. + */ +void PaletteImage:: +optimal_resize() { + if (is_empty()) { // && (_swapped_image == 0)) { + return; + } + + bool resized_any = false; + bool success; + do { + success = false; + nassertv(_x_size > 0 && _y_size > 0); + + // Try to cut it in half in both dimensions, one at a time. + if (resize_image(_x_size, _y_size / 2)) { + success = true; + resized_any = true; + } + if (resize_image(_x_size / 2, _y_size)) { + success = true; + resized_any = true; + } + + } while (success); + + if (resized_any) { + nout << "Resizing " + << FilenameUnifier::make_user_filename(get_filename()) << " to " + << _x_size << " " << _y_size << "\n"; + + // [gjeon] resize swapped images, also + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + swappedImage->resize_swapped_image(_x_size, _y_size); + } + } +} + +/** + * Attempts to resize the palette image, and repack all of the textures within + * the new size. Returns true if successful, false otherwise. If this fails, + * it will still result in repacking all the textures in the original size. + */ +bool PaletteImage:: +resize_image(int x_size, int y_size) { + // We already know we're going to be generating a new image from scratch + // after this. + _cleared_regions.clear(); + remove_image(); + + // First, Save the current placement list, while simultaneously clearing it. + Placements saved; + saved.swap(_placements); + + // Also save our current size. + int saved_x_size = _x_size; + int saved_y_size = _y_size; + + // Then, sort the textures to in order from biggest to smallest, as an aid + // to optimal packing. + sort(saved.begin(), saved.end(), SortPlacementBySize()); + + // And while we're at it, we need to officially unplace each of these. + Placements::iterator pi; + for (pi = saved.begin(); pi != saved.end(); ++pi) { + (*pi)->force_replace(); + } + + // Finally, apply the new size and try to fit all the textures. + _x_size = x_size; + _y_size = y_size; + + bool packed = true; + for (pi = saved.begin(); pi != saved.end() && packed; ++pi) { + if (!place(*pi)) { + packed = false; + } + } + + if (!packed) { + // If it didn't work, phooey. Put 'em all back. + _x_size = saved_x_size; + _y_size = saved_y_size; + + Placements remove; + remove.swap(_placements); + for (pi = remove.begin(); pi != remove.end(); ++pi) { + (*pi)->force_replace(); + } + + bool all_packed = true; + for (pi = saved.begin(); pi != saved.end(); ++pi) { + if (!place(*pi)) { + all_packed = false; + } + } + nassertr(all_packed, false); + } + + return packed; +} + +/** + * Attempts to resize the palette image, and repack all of the textures within + * the new size. Returns true if successful, false otherwise. If this fails, + * it will still result in repacking all the textures in the original size. + */ +void PaletteImage:: +resize_swapped_image(int x_size, int y_size) { + // Finally, apply the new size and try to fit all the textures. + _x_size = x_size; + _y_size = y_size; +} + +/** + * Writes a list of the textures that have been placed on this image to the + * indicated output stream, one per line. + */ +void PaletteImage:: +write_placements(std::ostream &out, int indent_level) const { + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + placement->write_placed(out, indent_level); + } +} + +/** + * Unpacks each texture that has been placed on this image, resetting the + * image to empty. + */ +void PaletteImage:: +reset_image() { + // We need a copy so we can modify this list as we traverse it. + Placements copy_placements = _placements; + Placements::const_iterator pi; + for (pi = copy_placements.begin(); pi != copy_placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + placement->force_replace(); + } + + _placements.clear(); + _cleared_regions.clear(); + remove_image(); +} + +/** + * Ensures the _shadow_image has the correct filename and image types, based + * on what was supplied on the command line and in the .txa file. + */ +void PaletteImage:: +setup_shadow_image() { + _shadow_image.make_shadow_image(_basename); + + // [gjeon] setup shadoe_image of swappedImages + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + swappedImage->setup_shadow_image(); + } +} + +/** + * If the palette has changed since it was last written out, updates the image + * and writes out a new one. If redo_all is true, regenerates the image from + * scratch and writes it out again, whether it needed it or not. + */ +void PaletteImage:: +update_image(bool redo_all) { + if (is_empty() && pal->_aggressively_clean_mapdir) { + // If the palette image is 'empty', ensure that it doesn't exist. No need + // to clutter up the map directory. + remove_image(); + return; + } + + if (redo_all) { + // If we're redoing everything, throw out the old image anyway. + remove_image(); + } + + // Check the filename too. + update_filename(); + + // Do we need to update? + bool needs_update = + _new_image || !exists() || + !_cleared_regions.empty(); + + Placements::iterator pi; + // We must continue to walk through all of the textures on the palette, even + // after we discover the palette requires an update, so we can determine + // which source images need to be recopied. + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + + if (!placement->is_filled()) { + needs_update = true; + + } else { + TextureImage *texture = placement->get_texture(); + + // Only check the timestamps on textures that are named (indirectly) on + // the command line. + if (texture->is_texture_named()) { + SourceTextureImage *source = texture->get_preferred_source(); + + if (source != nullptr && + source->get_filename().compare_timestamps(get_filename()) > 0) { + // The source image is newer than the palette image; we need to + // regenerate. + placement->mark_unfilled(); + needs_update = true; + } + } + + // [gjeon] to find out all of the swappable textures is up to date + TexturePlacement::TextureSwaps::iterator tsi; + for (tsi = placement->_textureSwaps.begin(); tsi != placement->_textureSwaps.end(); ++tsi) { + TextureImage *swapTexture = (*tsi); + + if (swapTexture->is_texture_named()) { + SourceTextureImage *sourceSwapTexture = swapTexture->get_preferred_source(); + + if (sourceSwapTexture != nullptr && + sourceSwapTexture->get_filename().compare_timestamps(get_filename()) > 0) { + // The source image is newer than the palette image; we need to + // regenerate. + placement->mark_unfilled(); + needs_update = true; + } + } + } + + } + } + + if (!needs_update) { + // No sweat; nothing has changed. + return; + } + + get_image(); + // [gjeon] get swapped images, too + get_swapped_images(); + + // Set to black any parts of the image that we recently unplaced. + ClearedRegions::iterator ci; + for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) { + ClearedRegion ®ion = (*ci); + region.clear(_image); + + // [gjeon] clear swapped images also + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + region.clear(swappedImage->_image); + } + } + _cleared_regions.clear(); + + // Now add the recent additions to the image. + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + if (!placement->is_filled()) { + placement->fill_image(_image); + + // [gjeon] fill swapped images + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + swappedImage->update_filename(); + placement->fill_swapped_image(swappedImage->_image, si - _swappedImages.begin()); + } + } + } + + write(_image); + + if (pal->_shadow_color_type != nullptr) { + _shadow_image.write(_image); + } + + release_image(); + + // [gjeon] write and release swapped images + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + swappedImage->write(swappedImage->_image); + if (pal->_shadow_color_type != nullptr) { + swappedImage->_shadow_image.write(swappedImage->_image); + } + swappedImage->release_image(); + } +} + +/** + * Changes the image filename to match the current naming scheme, assuming + * something has changed since the image was created. Returns true if the + * image filename changes (which means update_image() should be called). + */ +bool PaletteImage:: +update_filename() { + Filename orig_filename = _filename; + Filename orig_alpha_filename = _alpha_filename; + Filename orig_shadow_filename = _shadow_image.get_filename(); + + if (setup_filename()) { + nout << "Renaming " << FilenameUnifier::make_user_filename(orig_filename) + << " to " << FilenameUnifier::make_user_filename(_filename) << "\n"; + + if (!orig_filename.empty() && orig_filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(orig_filename) << "\n"; + orig_filename.unlink(); + } + if (!orig_alpha_filename.empty() && orig_alpha_filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(orig_alpha_filename) << "\n"; + orig_alpha_filename.unlink(); + } + if (!orig_shadow_filename.empty() && orig_shadow_filename.exists()) { + nout << "Deleting " << FilenameUnifier::make_user_filename(orig_shadow_filename) << "\n"; + orig_shadow_filename.unlink(); + } + _new_image = true; + + // Since the palette filename has changed, we need to mark all of the egg + // files that referenced the old filename as stale. + + // Marking egg files stale at this late point can cause minor problems; + // because we might do this, it's necessary for eggPalettize.cxx to call + // read_stale_eggs() twice. + Placements::iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + placement->mark_eggs_stale(); + } + + return true; + } + + return false; +} + +/** + * Sets up the image's filename (and that of the _shadow_pal) according to the + * specified properties. + * + * Returns true if the filename changes from what it was previously, false + * otherwise. + */ +bool PaletteImage:: +setup_filename() { + // Build up the basename for the palette image, based on the supplied image + // pattern. + _basename = std::string(); + + std::string::iterator si = pal->_generated_image_pattern.begin(); + while (si != pal->_generated_image_pattern.end()) { + if ((*si) == '%') { + // Some keycode. + ++si; + if (si != pal->_generated_image_pattern.end()) { + switch (*si) { + case '%': + _basename += '%'; + break; + + case 'g': + _basename += _page->get_group()->get_name(); + break; + + case 'p': + _basename += _page->get_name(); + break; + + case 'i': + _basename += format_string(_index + 1); + break; + + default: + _basename += '%'; + _basename += (*si); + } + ++si; + } + } else { + // A literal character. + _basename += (*si); + ++si; + } + } + + if (_swapped_image > 0) { + _basename += "_swp_"; + _basename += format_string(_swapped_image); + } + + // We must end the basename with a dot, so that it does not appear to have a + // filename extension. Otherwise, an embedded dot in the group's name would + // make everything following appear to be an extension, which would get lost + // in the set_filename() call. + if (_basename.empty() || _basename[_basename.length() - 1] != '.') { + _basename += '.'; + } + + bool any_changed = false; + + if (set_filename(_page->get_group(), _basename)) { + any_changed = true; + } + + if (_shadow_image.make_shadow_image(_basename)) { + any_changed = true; + } + + return any_changed; +} + +/** + * Searches for a hole of at least x_size by y_size pixels somewhere within + * the PaletteImage. If a suitable hole is found, sets x and y to the top + * left corner and returns true; otherwise, returns false. + */ +bool PaletteImage:: +find_hole(int &x, int &y, int x_size, int y_size) const { + y = 0; + while (y + y_size <= _y_size) { + int next_y = _y_size; + // Scan along the row at 'y'. + x = 0; + while (x + x_size <= _x_size) { + // Consider the spot at x, y. + TexturePlacement *overlap = find_overlap(x, y, x_size, y_size); + + if (overlap == nullptr) { + // Hooray! + return true; + } + + int next_x = overlap->get_placed_x() + overlap->get_placed_x_size(); + next_y = std::min(next_y, overlap->get_placed_y() + overlap->get_placed_y_size()); + nassertr(next_x > x, false); + x = next_x; + } + + nassertr(next_y > y, false); + y = next_y; + } + + // Nope, wouldn't fit anywhere. + return false; +} + +/** + * If the rectangle whose top left corner is x, y and whose size is x_size, + * y_size describes an empty hole that does not overlap any placed images, + * returns NULL; otherwise, returns the first placed texture that the image + * does overlap. It is assumed the rectangle lies completely within the + * boundaries of the image itself. + */ +TexturePlacement *PaletteImage:: +find_overlap(int x, int y, int x_size, int y_size) const { + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + if (placement->is_placed() && + placement->intersects(x, y, x_size, y_size)) { + return placement; + } + } + + return nullptr; +} + +/** + * Reads or generates the PNMImage that corresponds to the palette as it is + * known so far. + */ +void PaletteImage:: +get_image() { + if (_got_image) { + return; + } + + if (!_new_image) { + if (pal->_shadow_color_type != nullptr) { + if (_shadow_image.get_filename().exists() && _shadow_image.read(_image)) { + _got_image = true; + return; + } + } else { + if (get_filename().exists() && read(_image)) { + _got_image = true; + return; + } + } + } + + nout << "Generating new " + << FilenameUnifier::make_user_filename(get_filename()) << "\n"; + + // We won't be using this any more. + _cleared_regions.clear(); + + _image.clear(get_x_size(), get_y_size(), _properties.get_num_channels()); + _image.fill(pal->_background[0], pal->_background[1], pal->_background[2]); + if (_image.has_alpha()) { + _image.alpha_fill(pal->_background[3]); + } + + _new_image = false; + _got_image = true; + + // Now fill up the image. + Placements::iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + placement->fill_image(_image); + } +} + +/** + * Reads or generates the PNMImage for swapped textures + */ +void PaletteImage:: +get_swapped_image(int index) { + if (_got_image) { + return; + } + + if (!_new_image) { + if (pal->_shadow_color_type != nullptr) { + if (_shadow_image.get_filename().exists() && _shadow_image.read(_image)) { + _got_image = true; + return; + } + } else { + if (get_filename().exists() && read(_image)) { + _got_image = true; + return; + } + } + } + + nout << "Generating new " + << FilenameUnifier::make_user_filename(get_filename()) << "\n"; + + // We won't be using this any more. + _cleared_regions.clear(); + + _image.clear(get_x_size(), get_y_size(), _properties.get_num_channels()); + _image.fill(pal->_background[0], pal->_background[1], pal->_background[2]); + if (_image.has_alpha()) { + _image.alpha_fill(pal->_background[3]); + } + + _new_image = false; + _got_image = true; + + // Now fill up the image. + Placements::iterator pi; + for (pi = _masterPlacements->begin(); pi != _masterPlacements->end(); ++pi) { + TexturePlacement *placement = (*pi); + if ((int)placement->_textureSwaps.size() > index) { + placement->fill_swapped_image(_image, index); + } else { + placement->fill_image(_image); + } + } +} + +/** + * Reads or generates the PNMImage that corresponds to the palette as it is + * known so far. + */ +void PaletteImage:: +get_swapped_images() { + SwappedImages::iterator si; + for (si = _swappedImages.begin(); si != _swappedImages.end(); ++si) { + PaletteImage *swappedImage = (*si); + swappedImage->get_swapped_image(si - _swappedImages.begin()); + } +} + +/** + * Deallocates the memory allocated by a previous call to get_image(). + */ +void PaletteImage:: +release_image() { + _image.clear(); + _got_image = false; +} + +/** + * Deletes the image file. + */ +void PaletteImage:: +remove_image() { + unlink(); + if (pal->_shadow_color_type != nullptr) { + _shadow_image.unlink(); + } + _new_image = true; +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void PaletteImage:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void PaletteImage:: +write_datagram(BamWriter *writer, Datagram &datagram) { + ImageFile::write_datagram(writer, datagram); + + datagram.add_uint32(_cleared_regions.size()); + ClearedRegions::const_iterator ci; + for (ci = _cleared_regions.begin(); ci != _cleared_regions.end(); ++ci) { + (*ci).write_datagram(datagram); + } + + datagram.add_uint32(_placements.size()); + Placements::const_iterator pi; + for (pi = _placements.begin(); pi != _placements.end(); ++pi) { + writer->write_pointer(datagram, (*pi)); + } + + writer->write_pointer(datagram, _page); + datagram.add_uint32(_index); + datagram.add_string(_basename); + datagram.add_bool(_new_image); + + // We don't write _got_image or _image. These are loaded per-session. + + // We don't write _shadow_image. This is just a runtime convenience for + // specifying the name of the shadow file, and we redefine this per-session + // (which allows us to pick up a new pal->_shadow_dirname if it changes). +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int PaletteImage:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int index = ImageFile::complete_pointers(p_list, manager); + + int i; + _placements.reserve(_num_placements); + for (i = 0; i < _num_placements; i++) { + TexturePlacement *placement; + DCAST_INTO_R(placement, p_list[index], index); + _placements.push_back(placement); + index++; + } + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_page, p_list[index], index); + } + index++; + + return index; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *PaletteImage:: +make_from_bam(const FactoryParams ¶ms) { + PaletteImage *me = new PaletteImage; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void PaletteImage:: +fillin(DatagramIterator &scan, BamReader *manager) { + ImageFile::fillin(scan, manager); + + int num_cleared_regions = scan.get_uint32(); + _cleared_regions.reserve(num_cleared_regions); + for (int i = 0; i < num_cleared_regions; i++) { + _cleared_regions.push_back(ClearedRegion()); + _cleared_regions.back().fillin(scan); + } + + _num_placements = scan.get_uint32(); + manager->read_pointers(scan, _num_placements); + + manager->read_pointer(scan); // _page + + _index = scan.get_uint32(); + _basename = scan.get_string(); + _new_image = scan.get_bool(); +} diff --git a/pandatool/src/palettizer/paletteImage.h b/pandatool/src/palettizer/paletteImage.h new file mode 100644 index 00000000..cec32aa0 --- /dev/null +++ b/pandatool/src/palettizer/paletteImage.h @@ -0,0 +1,145 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file paletteImage.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef PALETTEIMAGE_H +#define PALETTEIMAGE_H + +#include "pandatoolbase.h" + +#include "imageFile.h" + +#include "pnmImage.h" + +class PalettePage; +class TexturePlacement; + +/** + * This is a single palette image, one of several within a PalettePage, which + * is in turn one of several pages within a PaletteGroup. Each palette image + * is a collage of several different textures that were all assigned to the + * same PaletteGroup, and all share the same properties of the PalettePage. + */ +class PaletteImage : public ImageFile { +private: + PaletteImage(); + +public: + PaletteImage(PalettePage *page, int index); + PaletteImage(PalettePage *page, int index, unsigned swapIndex); + + PalettePage *get_page() const; + + bool is_empty() const; + double count_utilization() const; + double count_coverage() const; + + bool place(TexturePlacement *placement); + void unplace(TexturePlacement *placement); + void check_solitary(); + + void optimal_resize(); + bool resize_image(int x_size, int y_size); + void resize_swapped_image(int x_size, int y_size); + + void write_placements(std::ostream &out, int indent_level = 0) const; + void reset_image(); + void setup_shadow_image(); + void update_image(bool redo_all); + + bool update_filename(); + +private: + bool setup_filename(); + bool find_hole(int &x, int &y, int x_size, int y_size) const; + TexturePlacement *find_overlap(int x, int y, int x_size, int y_size) const; + void get_image(); + void release_image(); + void remove_image(); + void get_swapped_image(int index); + void get_swapped_images(); + + // The ClearedRegion object keeps track of TexturePlacements that were + // recently removed and thus need to be set to black. + class ClearedRegion { + public: + ClearedRegion(); + ClearedRegion(TexturePlacement *placement); + ClearedRegion(const ClearedRegion ©); + void operator = (const ClearedRegion ©); + void clear(PNMImage &image); + + void write_datagram(Datagram &datagram) const; + void fillin(DatagramIterator &scan); + + private: + int _x, _y; + int _x_size, _y_size; + }; + + typedef pvector ClearedRegions; + ClearedRegions _cleared_regions; + + typedef pvector Placements; + Placements _placements; + + Placements *_masterPlacements; + + PalettePage *_page; + int _index; + std::string _basename; + + bool _new_image; + bool _got_image; + PNMImage _image; + + unsigned _swapped_image; // 0 for non swapped image + + ImageFile _shadow_image; + + typedef pvector SwappedImages; + SwappedImages _swappedImages; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // This value is only filled in while reading from the bam file; don't use + // it otherwise. + int _num_placements; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ImageFile::init_type(); + register_type(_type_handle, "PaletteImage", + ImageFile::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/palettizer/palettePage.cxx b/pandatool/src/palettizer/palettePage.cxx new file mode 100644 index 00000000..98fa9cb2 --- /dev/null +++ b/pandatool/src/palettizer/palettePage.cxx @@ -0,0 +1,299 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file palettePage.cxx + * @author drose + * @date 2000-12-01 + */ + +#include "palettePage.h" +#include "texturePlacement.h" +#include "textureImage.h" +#include "paletteImage.h" +#include "paletteGroup.h" + +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" + +#include + +TypeHandle PalettePage::_type_handle; + +/** + * The default constructor is only for the convenience of the Bam reader. + */ +PalettePage:: +PalettePage() { + _group = nullptr; +} + +/** + * + */ +PalettePage:: +PalettePage(PaletteGroup *group, const TextureProperties &properties) : + Namable(properties.get_string()), + _group(group), + _properties(properties) +{ +} + +/** + * Returns the group this particular PalettePage belongs to. + */ +PaletteGroup *PalettePage:: +get_group() const { + return _group; +} + +/** + * Returns the texture grouping properties that all textures in this page + * share. + */ +const TextureProperties &PalettePage:: +get_properties() const { + return _properties; +} + +/** + * Adds the indicated texture to the list of textures to consider placing on + * the page. + */ +void PalettePage:: +assign(TexturePlacement *placement) { + _assigned.push_back(placement); +} + + +/** + * Assigns all the textures to their final home in a PaletteImage somewhere. + */ +void PalettePage:: +place_all() { + // Sort the textures to be placed in order from biggest to smallest, as an + // aid to optimal packing. + sort(_assigned.begin(), _assigned.end(), SortPlacementBySize()); + + Assigned::const_iterator ai; + for (ai = _assigned.begin(); ai != _assigned.end(); ++ai) { + TexturePlacement *placement = (*ai); + place(placement); + } + + _assigned.clear(); + + // Now, look for solitary images; these are left placed, but flagged with + // OR_solitary, so they won't go into egg references. There's no real point + // in referencing these. + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + image->check_solitary(); + } +} + +/** + * Assigns the particular TexturePlacement to a PaletteImage where it fits. + */ +void PalettePage:: +place(TexturePlacement *placement) { + nassertv(placement->get_omit_reason() == OR_working); + + // First, try to place it in one of our existing PaletteImages. + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + if (image->place(placement)) { + return; + } + } + + // No good? Then we need to create a new PaletteImage for it. + PaletteImage *image = new PaletteImage(this, _images.size()); + _images.push_back(image); + + bool placed = image->place(placement); + + // This should have stuck. + nassertv(placed); +} + + +/** + * Removes the TexturePlacement from wherever it has been placed. + */ +void PalettePage:: +unplace(TexturePlacement *placement) { + nassertv(placement->is_placed() && placement->get_page() == this); + placement->get_image()->unplace(placement); +} + +/** + * Writes a list of the PaletteImages associated with this page, and all of + * their textures, to the indicated output stream. + */ +void PalettePage:: +write_image_info(std::ostream &out, int indent_level) const { + Images::const_iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + if (!image->is_empty()) { + indent(out, indent_level); + image->output_filename(out); + out << "\n"; + image->write_placements(out, indent_level + 2); + } + } +} + +/** + * Attempts to resize each PalettteImage down to its smallest possible size. + */ +void PalettePage:: +optimal_resize() { + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + image->optimal_resize(); + } +} + +/** + * Throws away all of the current PaletteImages, so that new ones may be + * created (and the packing made more optimal). + */ +void PalettePage:: +reset_images() { + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + image->reset_image(); + delete image; + } + + _images.clear(); +} + +/** + * Ensures that each PaletteImage's _shadow_image has the correct filename and + * image types, based on what was supplied on the command line and in the .txa + * file. + */ +void PalettePage:: +setup_shadow_images() { + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + image->setup_shadow_image(); + } +} + +/** + * Regenerates each PaletteImage on this page that needs it. + */ +void PalettePage:: +update_images(bool redo_all) { + Images::iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + PaletteImage *image = (*ii); + image->update_image(redo_all); + } +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void PalettePage:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void PalettePage:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_string(get_name()); + + writer->write_pointer(datagram, _group); + _properties.write_datagram(writer, datagram); + + // We don't write out _assigned, since that's rebuilt each session. + + datagram.add_uint32(_images.size()); + Images::const_iterator ii; + for (ii = _images.begin(); ii != _images.end(); ++ii) { + writer->write_pointer(datagram, *ii); + } +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int PalettePage:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + + if (p_list[pi] != nullptr) { + DCAST_INTO_R(_group, p_list[pi], pi); + } + pi++; + + pi += _properties.complete_pointers(p_list + pi, manager); + + int i; + _images.reserve(_num_images); + for (i = 0; i < _num_images; i++) { + PaletteImage *image; + DCAST_INTO_R(image, p_list[pi++], pi); + _images.push_back(image); + } + + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *PalettePage:: +make_from_bam(const FactoryParams ¶ms) { + PalettePage *me = new PalettePage; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void PalettePage:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + set_name(scan.get_string()); + + manager->read_pointer(scan); // _group + _properties.fillin(scan, manager); + + _num_images = scan.get_uint32(); + manager->read_pointers(scan, _num_images); +} diff --git a/pandatool/src/palettizer/palettePage.h b/pandatool/src/palettizer/palettePage.h new file mode 100644 index 00000000..8cac0f7a --- /dev/null +++ b/pandatool/src/palettizer/palettePage.h @@ -0,0 +1,99 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file palettePage.h + * @author drose + * @date 2000-12-01 + */ + +#ifndef PALETTEPAGE_H +#define PALETTEPAGE_H + +#include "pandatoolbase.h" + +#include "textureProperties.h" + +#include "namable.h" +#include "typedWritable.h" + +class PaletteGroup; +class PaletteImage; +class TexturePlacement; + +/** + * This is a particular collection of textures, within a PaletteGroup, that + * all share the same TextureProperties. The textures on the same page may + * therefore all be placed on the same set of PaletteImages together. + */ +class PalettePage : public TypedWritable, public Namable { +private: + PalettePage(); + +public: + PalettePage(PaletteGroup *group, const TextureProperties &properties); + + PaletteGroup *get_group() const; + const TextureProperties &get_properties() const; + + void assign(TexturePlacement *placement); + void place_all(); + void place(TexturePlacement *placement); + void unplace(TexturePlacement *placement); + + void write_image_info(std::ostream &out, int indent_level = 0) const; + void optimal_resize(); + void reset_images(); + void setup_shadow_images(); + void update_images(bool redo_all); + +private: + PaletteGroup *_group; + TextureProperties _properties; + + typedef pvector Assigned; + Assigned _assigned; + + typedef pvector Images; + Images _images; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // This value is only filled in while reading from the bam file; don't use + // it otherwise. + int _num_images; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + Namable::init_type(); + register_type(_type_handle, "PalettePage", + TypedWritable::get_class_type(), + Namable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/palettizer/palettizer.cxx b/pandatool/src/palettizer/palettizer.cxx new file mode 100644 index 00000000..0860cb38 --- /dev/null +++ b/pandatool/src/palettizer/palettizer.cxx @@ -0,0 +1,1175 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file palettizer.cxx + * @author drose + * @date 2000-12-01 + */ + +#include "palettizer.h" +#include "eggFile.h" +#include "textureImage.h" +#include "pal_string_utils.h" +#include "paletteGroup.h" +#include "filenameUnifier.h" +#include "textureMemoryCounter.h" + +#include "pnmImage.h" +#include "pnmFileTypeRegistry.h" +#include "pnmFileType.h" +#include "eggData.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "indent.h" + +using std::cout; +using std::string; + +Palettizer *pal = nullptr; + +// This number is written out as the first number to the pi file, to indicate +// the version of egg-palettize that wrote it out. This allows us to easily +// update egg-palettize to write out additional information to its pi file, +// without having it increment the bam version number for all bam and boo +// files anywhere in the world. +int Palettizer::_pi_version = 21; +/* + * Updated to version 8 on 32003 to remove extensions from texture key names. + * Updated to version 9 on 41303 to add a few properties in various places. + * Updated to version 10 on 41503 to add _alpha_file_channel. Updated to + * version 11 on 43003 to add TextureReference::_tref_name. Updated to + * version 12 on 91103 to add _generated_image_pattern. Updated to version 13 + * on 91303 to add _keep_format and _background. Updated to version 14 on + * 72605 to add _omit_everything. Updated to version 15 on 80105 to make + * TextureImages be case-insensitive. Updated to version 16 on 40306 to add + * Palettizer::_cutout_mode et al. Updated to version 17 on 30207 to add + * TextureImage::_txa_wrap_u etc. Updated to version 18 on 51308 to add + * TextureProperties::_quality_level. Updated to version 19 on 71609 to add + * PaletteGroup::_override_margin Updated to version 20 on 72709 to add + * TexturePlacement::_swapTextures + * Updated to version 21 on 110120 to add sRGB support. + */ + +int Palettizer::_min_pi_version = 8; +// Dropped support for versions 7 and below on 71403. + +int Palettizer::_read_pi_version = 0; + +TypeHandle Palettizer::_type_handle; + +std::ostream &operator << (std::ostream &out, Palettizer::RemapUV remap) { + switch (remap) { + case Palettizer::RU_never: + return out << "never"; + + case Palettizer::RU_group: + return out << "per group"; + + case Palettizer::RU_poly: + return out << "per polygon"; + + case Palettizer::RU_invalid: + return out << "(invalid)"; + } + + return out << "**invalid**(" << (int)remap << ")"; +} + + +// This STL function object is used in report_statistics(), below. +class SortGroupsByDependencyOrder { +public: + bool operator ()(PaletteGroup *a, PaletteGroup *b) { + if (a->get_dependency_order() != b->get_dependency_order()) { + return a->get_dependency_order() < b->get_dependency_order(); + } + return a->get_name() < b->get_name(); + } +}; + +// And this one is used in report_pi(). +class SortGroupsByPreference { +public: + bool operator ()(PaletteGroup *a, PaletteGroup *b) { + return !a->is_preferred_over(*b); + } +}; + +/** + * + */ +Palettizer:: +Palettizer() { + _is_valid = true; + _noabs = false; + + _generated_image_pattern = "%g_palette_%p_%i"; + _map_dirname = "%g"; + _shadow_dirname = "shadow"; + _margin = 2; + _omit_solitary = false; + _omit_everything = false; + _coverage_threshold = 2.5; + _aggressively_clean_mapdir = true; + _force_power_2 = true; + _color_type = PNMFileTypeRegistry::get_global_ptr()->get_type_from_extension("png"); + _alpha_type = nullptr; + _shadow_color_type = nullptr; + _shadow_alpha_type = nullptr; + _pal_x_size = _pal_y_size = 512; + _background.set(0.0, 0.0, 0.0, 0.0); + _cutout_mode = EggRenderMode::AM_dual; + _cutout_ratio = 0.3; + + _round_uvs = true; + _round_unit = 0.1; + _round_fuzz = 0.01; + _remap_uv = RU_poly; + _remap_char_uv = RU_poly; + + get_palette_group("null"); +} + +/** + * Returns the current setting of the noabs flag. See set_noabs(). + */ +bool Palettizer:: +get_noabs() const { + return _noabs; +} + +/** + * Changes the current setting of the noabs flag. + * + * If this flag is true, then it is an error to process an egg file that + * contains absolute pathname references. This flag is intended to help + * detect egg files that are incorrectly built within a model tree (which + * should use entirely relative pathnames). + * + * This flag must be set before any egg files are processed. + */ +void Palettizer:: +set_noabs(bool noabs) { + _noabs = noabs; +} + +/** + * Returns true if the palette information file was read correctly, or false + * if there was some error and the palettization can't continue. + */ +bool Palettizer:: +is_valid() const { + return _is_valid; +} + +/** + * Output a verbose description of all the palettization information to + * standard output, for the user's perusal. + */ +void Palettizer:: +report_pi() const { + // Start out with the cross links and back counts; some of these are nice to + // report. + EggFiles::const_iterator efi; + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + (*efi).second->build_cross_links(); + } + + cout + << "\nparams\n" + << " generated image pattern: " << _generated_image_pattern << "\n" + << " map directory: " << _map_dirname << "\n" + << " shadow directory: " + << FilenameUnifier::make_user_filename(_shadow_dirname) << "\n" + << " egg relative directory: " + << FilenameUnifier::make_user_filename(_rel_dirname) << "\n" + << " palettize size: " << _pal_x_size << " by " << _pal_y_size << "\n" + << " background: " << _background << "\n" + << " margin: " << _margin << "\n" + << " coverage threshold: " << _coverage_threshold << "\n" + << " force textures to power of 2: " << yesno(_force_power_2) << "\n" + << " aggressively clean the map directory: " + << yesno(_aggressively_clean_mapdir) << "\n" + << " omit everything: " << yesno(_omit_everything) << "\n" + << " round UV area: " << yesno(_round_uvs) << "\n"; + if (_round_uvs) { + cout << " round UV area to nearest " << _round_unit << " with fuzz " + << _round_fuzz << "\n"; + } + cout << " remap UV's: " << _remap_uv << "\n" + << " remap UV's for characters: " << _remap_char_uv << "\n"; + cout << " alpha cutouts: " << _cutout_mode << " " << _cutout_ratio << "\n"; + + if (_color_type != nullptr) { + cout << " generate image files of type: " + << _color_type->get_suggested_extension(); + if (_alpha_type != nullptr) { + cout << "," << _alpha_type->get_suggested_extension(); + } + cout << "\n"; + } + + if (_shadow_color_type != nullptr) { + cout << " generate shadow palette files of type: " + << _shadow_color_type->get_suggested_extension(); + if (_shadow_alpha_type != nullptr) { + cout << "," << _shadow_alpha_type->get_suggested_extension(); + } + cout << "\n"; + } + + cout << "\ntexture source pathnames and assignments\n"; + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + if (texture->is_used()) { + cout << " " << texture->get_name() << ":\n"; + texture->write_source_pathnames(cout, 4); + } + } + + cout << "\negg files and textures referenced\n"; + EggFiles::const_iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + EggFile *egg_file = (*ei).second; + egg_file->write_description(cout, 2); + egg_file->write_texture_refs(cout, 4); + } + + // Sort the palette groups into order of preference, so that the more + // specific ones appear at the bottom. + pvector sorted_groups; + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + sorted_groups.push_back((*gi).second); + } + sort(sorted_groups.begin(), sorted_groups.end(), + SortGroupsByPreference()); + + cout << "\npalette groups\n"; + pvector::iterator si; + for (si = sorted_groups.begin(); si != sorted_groups.end(); ++si) { + PaletteGroup *group = (*si); + if (si != sorted_groups.begin()) { + cout << "\n"; + } + cout << " " << group->get_name() + // << " (" << group->get_dirname_order() << "," << + // group->get_dependency_order() << ")" + << ": " << group->get_groups() << "\n"; + group->write_image_info(cout, 4); + } + + cout << "\ntextures\n"; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + texture->write_scale_info(cout, 2); + } + + cout << "\nsurprises\n"; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + if (texture->is_surprise()) { + cout << " " << texture->get_name() << "\n"; + } + } + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + EggFile *egg_file = (*ei).second; + if (egg_file->is_surprise()) { + cout << " " << egg_file->get_name() << "\n"; + } + } + + cout << "\n"; +} + +/** + * Output a report of the palettization effectiveness, texture memory + * utilization, and so on. + */ +void Palettizer:: +report_statistics() const { + // Sort the groups into order by dependency order, for the user's + // convenience. + pvector sorted_groups; + + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + sorted_groups.push_back((*gi).second); + } + + sort(sorted_groups.begin(), sorted_groups.end(), + SortGroupsByDependencyOrder()); + + Placements overall_placements; + + pvector::const_iterator si; + for (si = sorted_groups.begin(); + si != sorted_groups.end(); + ++si) { + PaletteGroup *group = (*si); + + Placements placements; + group->get_placements(placements); + if (!placements.empty()) { + group->get_placements(overall_placements); + + cout << "\n" << group->get_name() << ", by itself:\n"; + compute_statistics(cout, 2, placements); + + PaletteGroups complete; + complete.make_complete(group->get_groups()); + + if (complete.size() > 1) { + Placements complete_placements; + group->get_complete_placements(complete_placements); + if (complete_placements.size() != placements.size()) { + cout << "\n" << group->get_name() + << ", with dependents (" << complete << "):\n"; + compute_statistics(cout, 2, complete_placements); + } + } + } + } + + cout << "\nOverall:\n"; + compute_statistics(cout, 2, overall_placements); + + cout << "\n"; +} + + +/** + * Reads in the .txa file and keeps it ready for matching textures and egg + * files. + */ +void Palettizer:: +read_txa_file(std::istream &txa_file, const string &txa_filename) { + // Clear out the group dependencies, in preparation for reading them again + // from the .txa file. + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->clear_depends(); + group->set_dirname(""); + } + + // Also reset _shadow_color_type. + _shadow_color_type = nullptr; + _shadow_alpha_type = nullptr; + + if (!_txa_file.read(txa_file, txa_filename)) { + exit(1); + } + + if (_color_type == nullptr) { + nout << "No valid output image file type available; cannot run.\n" + << "Use :imagetype command in .txa file.\n"; + exit(1); + } + + // Compute the correct dependency level and order for each group. This will + // help us when we assign the textures to their groups. + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->reset_dependency_level(); + } + + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->set_dependency_level(1); + } + + bool any_changed; + do { + any_changed = false; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + if (group->set_dependency_order()) { + any_changed = true; + } + } + } while (any_changed); +} + +/** + * Called after all command line parameters have been set up, this is a hook + * to do whatever initialization is necessary. + */ +void Palettizer:: +all_params_set() { + // Make sure the palettes have their shadow images set up properly. + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->setup_shadow_images(); + } +} + +/** + * Processes all the textures named in the _command_line_eggs, placing them on + * the appropriate palettes or whatever needs to be done with them. + * + * If force_texture_read is true, it forces each texture image file to be read + * (and thus legitimately checked for grayscaleness etc.) before placing. + */ +void Palettizer:: +process_command_line_eggs(bool force_texture_read, const Filename &state_filename) { + _command_line_textures.clear(); + + // Start by scanning all the egg files we read up on the command line. + CommandLineEggs::const_iterator ei; + for (ei = _command_line_eggs.begin(); + ei != _command_line_eggs.end(); + ++ei) { + EggFile *egg_file = (*ei); + + egg_file->scan_textures(); + egg_file->get_textures(_command_line_textures); + + egg_file->pre_txa_file(); + _txa_file.match_egg(egg_file); + egg_file->post_txa_file(); + } + + // Now that all of our egg files are read in, build in all the cross links + // and back pointers and stuff. + EggFiles::const_iterator efi; + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + (*efi).second->build_cross_links(); + } + + // Now match each of the textures mentioned in those egg files against a + // line in the .txa file. + CommandLineTextures::iterator ti; + for (ti = _command_line_textures.begin(); + ti != _command_line_textures.end(); + ++ti) { + TextureImage *texture = *ti; + + if (force_texture_read || texture->is_newer_than(state_filename)) { + // If we're forcing a redo, or the texture image has changed, re-read + // the complete image. + texture->read_source_image(); + } else { + // Otherwise, just the header is sufficient. + texture->read_header(); + } + + texture->mark_texture_named(); + texture->pre_txa_file(); + _txa_file.match_texture(texture); + texture->post_txa_file(); + } + + // And now, assign each of the current set of textures to an appropriate + // group or groups. + for (ti = _command_line_textures.begin(); + ti != _command_line_textures.end(); + ++ti) { + TextureImage *texture = *ti; + texture->assign_groups(); + } + + // And then the egg files need to sign up for a particular TexturePlacement, + // so we can determine some more properties about how the textures are + // placed (for instance, how big the UV range is for a particular + // TexturePlacement). + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + (*efi).second->choose_placements(); + } + + // Now that *that's* done, we need to make sure the various + // TexturePlacements require the right size for their textures. + for (ti = _command_line_textures.begin(); + ti != _command_line_textures.end(); + ++ti) { + TextureImage *texture = *ti; + texture->determine_placement_size(); + } + + // Now that each texture has been assigned to a suitable group, make sure + // the textures are placed on specific PaletteImages. + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->update_unknown_textures(_txa_file); + group->place_all(); + } +} + +/** + * Reprocesses all textures known. + * + * If force_texture_read is true, it forces each texture image file to be read + * (and thus legitimately checked for grayscaleness etc.) before placing. + */ +void Palettizer:: +process_all(bool force_texture_read, const Filename &state_filename) { + // First, clear all the basic properties on the source texture images, so we + // can reapply them from the complete set of egg files and thereby ensure + // they are up-to-date. + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + texture->clear_source_basic_properties(); + } + + // If there *were* any egg files on the command line, deal with them. + CommandLineEggs::const_iterator ei; + for (ei = _command_line_eggs.begin(); + ei != _command_line_eggs.end(); + ++ei) { + EggFile *egg_file = (*ei); + + egg_file->scan_textures(); + egg_file->get_textures(_command_line_textures); + } + + // Then match up all the egg files we know about with the .txa file. + EggFiles::const_iterator efi; + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + EggFile *egg_file = (*efi).second; + egg_file->pre_txa_file(); + _txa_file.match_egg(egg_file); + egg_file->post_txa_file(); + } + + // Now that all of our egg files are read in, build in all the cross links + // and back pointers and stuff. + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + (*efi).second->build_cross_links(); + + // Also make sure each egg file's properties are applied to the source + // image (since we reset all the source image properties, above). + (*efi).second->apply_properties_to_source(); + } + + // Now match each of the textures in the world against a line in the .txa + // file. + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + if (force_texture_read || texture->is_newer_than(state_filename)) { + texture->read_source_image(); + } + + texture->mark_texture_named(); + texture->pre_txa_file(); + _txa_file.match_texture(texture); + texture->post_txa_file(); + + // We need to do this to avoid bloating memory. + texture->release_source_image(); + } + + // And now, assign each texture to an appropriate group or groups. + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + texture->assign_groups(); + } + + // And then the egg files need to sign up for a particular TexturePlacement, + // so we can determine some more properties about how the textures are + // placed (for instance, how big the UV range is for a particular + // TexturePlacement). + for (efi = _egg_files.begin(); efi != _egg_files.end(); ++efi) { + (*efi).second->choose_placements(); + } + + // Now that *that's* done, we need to make sure the various + // TexturePlacements require the right size for their textures. + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + texture->determine_placement_size(); + } + + // Now that each texture has been assigned to a suitable group, make sure + // the textures are placed on specific PaletteImages. + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->update_unknown_textures(_txa_file); + group->place_all(); + } +} + +/** + * Attempts to resize each PalettteImage down to its smallest possible size. + */ +void Palettizer:: +optimal_resize() { + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->optimal_resize(); + } +} + +/** + * Throws away all of the current PaletteImages, so that new ones may be + * created (and the packing made more optimal). + */ +void Palettizer:: +reset_images() { + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->reset_images(); + } +} + +/** + * Actually generates the appropriate palette and unplaced texture images into + * the map directories. If redo_all is true, this forces a regeneration of + * each image file. + */ +void Palettizer:: +generate_images(bool redo_all) { + Groups::iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + PaletteGroup *group = (*gi).second; + group->update_images(redo_all); + } + + Textures::iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + TextureImage *texture = (*ti).second; + texture->copy_unplaced(redo_all); + } +} + +/** + * Reads in any egg file that is known to be stale, even if it was not listed + * on the command line, so that it may be updated and written out when + * write_eggs() is called. If redo_all is true, this even reads egg files + * that were not flagged as stale. + * + * Returns true if successful, or false if there was some error. + */ +bool Palettizer:: +read_stale_eggs(bool redo_all) { + bool okflag = true; + + pvector invalid_eggs; + + EggFiles::iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + EggFile *egg_file = (*ei).second; + if (!egg_file->had_data() && + (egg_file->is_stale() || redo_all)) { + if (!egg_file->read_egg(_noabs)) { + invalid_eggs.push_back(ei); + + } else { + egg_file->scan_textures(); + egg_file->choose_placements(); + egg_file->release_egg_data(); + } + } + } + + // Now eliminate all the invalid egg files. + pvector::iterator ii; + for (ii = invalid_eggs.begin(); ii != invalid_eggs.end(); ++ii) { + EggFiles::iterator ei = (*ii); + EggFile *egg_file = (*ei).second; + if (egg_file->get_source_filename().exists()) { + // If there is an invalid egg file, remove it; hopefully it will get + // rebuilt properly next time. + nout << "Removing invalid egg file: " + << FilenameUnifier::make_user_filename(egg_file->get_source_filename()) + << "\n"; + + egg_file->get_source_filename().unlink(); + okflag = false; + + } else { + // If the egg file is simply missing, quietly remove any record of it + // from the database. + egg_file->remove_egg(); + _egg_files.erase(ei); + } + } + + if (!okflag) { + nout << "\n" + << "Some errors in egg files encountered.\n" + << "Re-run make install or make opt-pal to try to regenerate these.\n\n"; + } + + return okflag; +} + +/** + * Adjusts the egg files to reference the newly generated textures, and writes + * them out. Returns true if successful, or false if there was some error. + */ +bool Palettizer:: +write_eggs() { + bool okflag = true; + + EggFiles::iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + EggFile *egg_file = (*ei).second; + if (egg_file->had_data()) { + if (!egg_file->has_data()) { + // Re-read the egg file. + bool read_ok = egg_file->read_egg(_noabs); + if (!read_ok) { + nout << "Error! Unable to re-read egg file.\n"; + okflag = false; + } + } + + if (egg_file->has_data()) { + egg_file->update_egg(); + if (!egg_file->write_egg()) { + okflag = false; + } + egg_file->release_egg_data(); + } + } + } + + return okflag; +} + +/** + * Returns the EggFile with the given name. If there is no EggFile with the + * indicated name, creates one. This is the key name used to sort the egg + * files, which is typically the basename of the filename. + */ +EggFile *Palettizer:: +get_egg_file(const string &name) { + EggFiles::iterator ei = _egg_files.find(name); + if (ei != _egg_files.end()) { + return (*ei).second; + } + + EggFile *file = new EggFile; + file->set_name(name); + _egg_files.insert(EggFiles::value_type(name, file)); + return file; +} + +/** + * Removes the named egg file from the database, if it exists. Returns true + * if the egg file was found, false if it was not. + */ +bool Palettizer:: +remove_egg_file(const string &name) { + EggFiles::iterator ei = _egg_files.find(name); + if (ei != _egg_files.end()) { + EggFile *file = (*ei).second; + file->remove_egg(); + _egg_files.erase(ei); + return true; + } + + return false; +} + +/** + * Adds the indicated EggFile to the list of eggs that are considered to have + * been read on the command line. These will be processed by + * process_command_line_eggs(). + */ +void Palettizer:: +add_command_line_egg(EggFile *egg_file) { + _command_line_eggs.push_back(egg_file); +} + +/** + * Returns the PaletteGroup with the given name. If there is no PaletteGroup + * with the indicated name, creates one. + */ +PaletteGroup *Palettizer:: +get_palette_group(const string &name) { + Groups::iterator gi = _groups.find(name); + if (gi != _groups.end()) { + return (*gi).second; + } + + PaletteGroup *group = new PaletteGroup; + group->set_name(name); + _groups.insert(Groups::value_type(name, group)); + return group; +} + +/** + * Returns the PaletteGroup with the given name. If there is no PaletteGroup + * with the indicated name, returns NULL. + */ +PaletteGroup *Palettizer:: +test_palette_group(const string &name) const { + Groups::const_iterator gi = _groups.find(name); + if (gi != _groups.end()) { + return (*gi).second; + } + + return nullptr; +} + +/** + * Returns the default group to which an egg file should be assigned if it is + * not mentioned in the .txa file. + */ +PaletteGroup *Palettizer:: +get_default_group() { + PaletteGroup *default_group = get_palette_group(_default_groupname); + if (!_default_groupdir.empty() && !default_group->has_dirname()) { + default_group->set_dirname(_default_groupdir); + } + return default_group; +} + +/** + * Returns the TextureImage with the given name. If there is no TextureImage + * with the indicated name, creates one. This is the key name used to sort + * the textures, which is typically the basename of the primary filename. + */ +TextureImage *Palettizer:: +get_texture(const string &name) { + // Look first in the same-case name, just in case it happens to be there + // (from an older version of egg-palettize that did this). + Textures::iterator ti = _textures.find(name); + if (ti != _textures.end()) { + return (*ti).second; + } + + // Then look in the downcase name, since we nowadays index textures only by + // their downcase names (to implement case insensitivity). + string downcase_name = downcase(name); + ti = _textures.find(downcase_name); + if (ti != _textures.end()) { + return (*ti).second; + } + + TextureImage *image = new TextureImage; + image->set_name(name); + // image->set_filename(name); + _textures.insert(Textures::value_type(downcase_name, image)); + + return image; +} + +/** + * A silly function to return "yes" or "no" based on a bool flag for nicely + * formatted output. + */ +const char *Palettizer:: +yesno(bool flag) { + return flag ? "yes" : "no"; +} + +/** + * Returns the RemapUV code corresponding to the indicated string, or + * RU_invalid if the string is invalid. + */ +Palettizer::RemapUV Palettizer:: +string_remap(const string &str) { + if (str == "never") { + return RU_never; + + } else if (str == "group") { + return RU_group; + + } else if (str == "poly") { + return RU_poly; + + } else { + return RU_invalid; + } +} + +/** + * Determines how much memory, etc. is required by the indicated set of + * texture placements, and reports this to the indicated output stream. + */ +void Palettizer:: +compute_statistics(std::ostream &out, int indent_level, + const Palettizer::Placements &placements) const { + TextureMemoryCounter counter; + + Placements::const_iterator pi; + for (pi = placements.begin(); pi != placements.end(); ++pi) { + TexturePlacement *placement = (*pi); + counter.add_placement(placement); + } + + counter.report(out, indent_level); +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void Palettizer:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void Palettizer:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + + datagram.add_int32(_pi_version); + datagram.add_string(_generated_image_pattern); + datagram.add_string(_map_dirname); + datagram.add_string(FilenameUnifier::make_bam_filename(_shadow_dirname)); + datagram.add_string(FilenameUnifier::make_bam_filename(_rel_dirname)); + datagram.add_int32(_pal_x_size); + datagram.add_int32(_pal_y_size); + datagram.add_float64(_background[0]); + datagram.add_float64(_background[1]); + datagram.add_float64(_background[2]); + datagram.add_float64(_background[3]); + datagram.add_int32(_margin); + datagram.add_bool(_omit_solitary); + datagram.add_bool(_omit_everything); + datagram.add_float64(_coverage_threshold); + datagram.add_bool(_force_power_2); + datagram.add_bool(_aggressively_clean_mapdir); + datagram.add_bool(_round_uvs); + datagram.add_float64(_round_unit); + datagram.add_float64(_round_fuzz); + datagram.add_int32((int)_remap_uv); + datagram.add_int32((int)_remap_char_uv); + datagram.add_uint8((int)_cutout_mode); + datagram.add_float64(_cutout_ratio); + + writer->write_pointer(datagram, _color_type); + writer->write_pointer(datagram, _alpha_type); + writer->write_pointer(datagram, _shadow_color_type); + writer->write_pointer(datagram, _shadow_alpha_type); + + datagram.add_int32(_egg_files.size()); + EggFiles::const_iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + writer->write_pointer(datagram, (*ei).second); + } + + // We don't write _command_line_eggs; that's specific to each session. + + datagram.add_int32(_groups.size()); + Groups::const_iterator gi; + for (gi = _groups.begin(); gi != _groups.end(); ++gi) { + writer->write_pointer(datagram, (*gi).second); + } + + datagram.add_int32(_textures.size()); + Textures::const_iterator ti; + for (ti = _textures.begin(); ti != _textures.end(); ++ti) { + writer->write_pointer(datagram, (*ti).second); + } +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int Palettizer:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int index = TypedWritable::complete_pointers(p_list, manager); + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_color_type, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_alpha_type, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_shadow_color_type, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_shadow_alpha_type, p_list[index], index); + } + index++; + + int i; + for (i = 0; i < _num_egg_files; i++) { + EggFile *egg_file; + DCAST_INTO_R(egg_file, p_list[index], index); + _egg_files.insert(EggFiles::value_type(egg_file->get_name(), egg_file)); + index++; + } + + for (i = 0; i < _num_groups; i++) { + PaletteGroup *group; + DCAST_INTO_R(group, p_list[index], index); + _groups.insert(Groups::value_type(group->get_name(), group)); + index++; + } + + for (i = 0; i < _num_textures; i++) { + TextureImage *texture; + DCAST_INTO_R(texture, p_list[index], index); + + string name = downcase(texture->get_name()); + std::pair result = _textures.insert(Textures::value_type(name, texture)); + if (!result.second) { + // Two textures mapped to the same slot--probably a case error (since we + // just changed this rule). + _texture_conflicts.push_back(texture); + } + index++; + } + + return index; +} + +/** + * Called by the BamReader to perform any final actions needed for setting up + * the object after all objects have been read and all pointers have been + * completed. + */ +void Palettizer:: +finalize(BamReader *manager) { + // Walk through the list of texture names that were in conflict. These can + // only happen if there were two different names that different only in + // case, which means the textures.boo file was created before we introduced + // the rule that case is insignificant. + TextureConflicts::iterator ci; + for (ci = _texture_conflicts.begin(); + ci != _texture_conflicts.end(); + ++ci) { + TextureImage *texture_b = (*ci); + string downcase_name = downcase(texture_b->get_name()); + + Textures::iterator ti = _textures.find(downcase_name); + nassertv(ti != _textures.end()); + TextureImage *texture_a = (*ti).second; + _textures.erase(ti); + + if (!texture_b->is_used() || !texture_a->is_used()) { + // If either texture is not used, there's not really a conflict--the + // other one wins. + if (texture_a->is_used()) { + bool inserted1 = _textures.insert(Textures::value_type(downcase_name, texture_a)).second; + nassertd(inserted1) { } + + } else if (texture_b->is_used()) { + bool inserted2 = _textures.insert(Textures::value_type(downcase_name, texture_b)).second; + nassertd(inserted2) { } + } + + } else { + // If both textures are used, there *is* a conflict. + nout << "Texture name conflict: \"" << texture_a->get_name() + << "\" vs. \"" << texture_b->get_name() << "\"\n"; + if (texture_a->get_name() != downcase_name && + texture_b->get_name() != downcase_name) { + // Arbitrarily pick texture_a to get the right case. + bool inserted1 = _textures.insert(Textures::value_type(downcase_name, texture_a)).second; + bool inserted2 = _textures.insert(Textures::value_type(texture_b->get_name(), texture_b)).second; + nassertd(inserted1 && inserted2) { } + + } else { + // One of them is already the right case. + bool inserted1 = _textures.insert(Textures::value_type(texture_a->get_name(), texture_a)).second; + bool inserted2 = _textures.insert(Textures::value_type(texture_b->get_name(), texture_b)).second; + nassertd(inserted1 && inserted2) { } + } + } + } +} + + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *Palettizer:: +make_from_bam(const FactoryParams ¶ms) { + Palettizer *me = new Palettizer; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + manager->register_finalize(me); + + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void Palettizer:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + + _read_pi_version = scan.get_int32(); + if (_read_pi_version > _pi_version || _read_pi_version < _min_pi_version) { + // Oops, we don't know how to read this palette information file. + _is_valid = false; + return; + } + if (_read_pi_version >= 12) { + _generated_image_pattern = scan.get_string(); + } + _map_dirname = scan.get_string(); + _shadow_dirname = FilenameUnifier::get_bam_filename(scan.get_string()); + _rel_dirname = FilenameUnifier::get_bam_filename(scan.get_string()); + FilenameUnifier::set_rel_dirname(_rel_dirname); + _pal_x_size = scan.get_int32(); + _pal_y_size = scan.get_int32(); + if (_read_pi_version >= 13) { + _background[0] = scan.get_float64(); + _background[1] = scan.get_float64(); + _background[2] = scan.get_float64(); + _background[3] = scan.get_float64(); + } + _margin = scan.get_int32(); + _omit_solitary = scan.get_bool(); + if (_read_pi_version >= 14) { + _omit_everything = scan.get_bool(); + } + _coverage_threshold = scan.get_float64(); + _force_power_2 = scan.get_bool(); + _aggressively_clean_mapdir = scan.get_bool(); + _round_uvs = scan.get_bool(); + _round_unit = scan.get_float64(); + _round_fuzz = scan.get_float64(); + _remap_uv = (RemapUV)scan.get_int32(); + _remap_char_uv = (RemapUV)scan.get_int32(); + if (_read_pi_version >= 16) { + _cutout_mode = (EggRenderMode::AlphaMode)scan.get_uint8(); + _cutout_ratio = scan.get_float64(); + } + + manager->read_pointer(scan); // _color_type + manager->read_pointer(scan); // _alpha_type + manager->read_pointer(scan); // _shadow_color_type + manager->read_pointer(scan); // _shadow_alpha_type + + _num_egg_files = scan.get_int32(); + manager->read_pointers(scan, _num_egg_files); + + _num_groups = scan.get_int32(); + manager->read_pointers(scan, _num_groups); + + _num_textures = scan.get_int32(); + manager->read_pointers(scan, _num_textures); +} diff --git a/pandatool/src/palettizer/palettizer.h b/pandatool/src/palettizer/palettizer.h new file mode 100644 index 00000000..93f211bb --- /dev/null +++ b/pandatool/src/palettizer/palettizer.h @@ -0,0 +1,190 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file palettizer.h + * @author drose + * @date 2000-12-01 + */ + +#ifndef PALETTIZER_H +#define PALETTIZER_H + +#include "pandatoolbase.h" + +#include "txaFile.h" + +#include "typedWritable.h" +#include "eggRenderMode.h" +#include "pvector.h" +#include "pset.h" +#include "pmap.h" + +class PNMFileType; +class EggFile; +class PaletteGroup; +class TextureImage; +class TexturePlacement; +class FactoryParams; + +/** + * This is the main engine behind egg-palettize. It contains all of the + * program parameters, from the command line or saved from a previous session, + * and serves as the driving force in the actual palettizing process. + */ +class Palettizer : public TypedWritable { +public: + Palettizer(); + + bool get_noabs() const; + void set_noabs(bool noabs); + + bool is_valid() const; + void report_pi() const; + void report_statistics() const; + + void read_txa_file(std::istream &txa_file, const std::string &txa_filename); + void all_params_set(); + void process_command_line_eggs(bool force_texture_read, const Filename &state_filename); + void process_all(bool force_texture_read, const Filename &state_filename); + void optimal_resize(); + void reset_images(); + void generate_images(bool redo_all); + bool read_stale_eggs(bool redo_all); + bool write_eggs(); + + EggFile *get_egg_file(const std::string &name); + bool remove_egg_file(const std::string &name); + + void add_command_line_egg(EggFile *egg_file); + + PaletteGroup *get_palette_group(const std::string &name); + PaletteGroup *test_palette_group(const std::string &name) const; + PaletteGroup *get_default_group(); + TextureImage *get_texture(const std::string &name); + +private: + static const char *yesno(bool flag); + +public: + static int _pi_version; + static int _min_pi_version; + static int _read_pi_version; + + enum RemapUV { + RU_never, + RU_group, + RU_poly, + RU_invalid + }; + + static RemapUV string_remap(const std::string &str); + + bool _is_valid; + + // These values are not stored in the textures.boo file, but are specific to + // each session. + TxaFile _txa_file; + std::string _default_groupname; + std::string _default_groupdir; + bool _noabs; + + // The following parameter values specifically relate to textures and + // palettes. These values are stored in the textures.boo file for future + // reference. + std::string _generated_image_pattern; + std::string _map_dirname; + Filename _shadow_dirname; + Filename _rel_dirname; + int _pal_x_size, _pal_y_size; + LColord _background; + int _margin; + bool _omit_solitary; + bool _omit_everything; + double _coverage_threshold; + bool _force_power_2; + bool _aggressively_clean_mapdir; + bool _round_uvs; + double _round_unit; + double _round_fuzz; + RemapUV _remap_uv, _remap_char_uv; + PNMFileType *_color_type; + PNMFileType *_alpha_type; + PNMFileType *_shadow_color_type; + PNMFileType *_shadow_alpha_type; + EggRenderMode::AlphaMode _cutout_mode; + double _cutout_ratio; + +private: + typedef pvector Placements; + void compute_statistics(std::ostream &out, int indent_level, + const Placements &placements) const; + + typedef pmap EggFiles; + EggFiles _egg_files; + + typedef pvector CommandLineEggs; + CommandLineEggs _command_line_eggs; + + typedef pset CommandLineTextures; + CommandLineTextures _command_line_textures; + + typedef pmap Groups; + Groups _groups; + + typedef pmap Textures; + Textures _textures; + typedef pvector TextureConflicts; + TextureConflicts _texture_conflicts; + + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + + virtual void finalize(BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // These values are only filled in while reading from the bam file; don't + // use them otherwise. + int _num_egg_files; + int _num_groups; + int _num_textures; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "Palettizer", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; + + friend class TxaLine; +}; + +// This is a global Palettizer pointer that may be filled in when the +// Palettizer is created, for convenience in referencing it from multiple +// places. (Generally, a standalone program will only create one Palettizer +// object in a session.) +extern Palettizer *pal; + +#endif diff --git a/pandatool/src/palettizer/sourceTextureImage.cxx b/pandatool/src/palettizer/sourceTextureImage.cxx new file mode 100644 index 00000000..c3aac422 --- /dev/null +++ b/pandatool/src/palettizer/sourceTextureImage.cxx @@ -0,0 +1,209 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file sourceTextureImage.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "sourceTextureImage.h" +#include "textureImage.h" +#include "filenameUnifier.h" + +#include "pnmImageHeader.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" + +TypeHandle SourceTextureImage::_type_handle; + +/** + * The default constructor is only for the convenience of the Bam reader. + */ +SourceTextureImage:: +SourceTextureImage() { + _texture = nullptr; + + _egg_count = 0; + _read_header = false; + _successfully_read_header = false; +} + +/** + * + */ +SourceTextureImage:: +SourceTextureImage(TextureImage *texture, const Filename &filename, + const Filename &alpha_filename, int alpha_file_channel) : + _texture(texture) +{ + _filename = filename; + _alpha_filename = alpha_filename; + _alpha_file_channel = alpha_file_channel; + _egg_count = 0; + _read_header = false; + _successfully_read_header = false; +} + +/** + * Returns the particular texture that this image is one of the sources for. + */ +TextureImage *SourceTextureImage:: +get_texture() const { + return _texture; +} + +/** + * Increments by one the number of egg files that are known to reference this + * SourceTextureImage. + */ +void SourceTextureImage:: +increment_egg_count() { + _egg_count++; +} + +/** + * Returns the number of egg files that share this SourceTextureImage. + */ +int SourceTextureImage:: +get_egg_count() const { + return _egg_count; +} + +/** + * Determines the size of the SourceTextureImage, if it is not already known. + * Returns true if the size was successfully determined (or if was already + * known), or false if the size could not be determined (for instance, because + * the image file is missing). After this call returns true, get_x_size() + * etc. may be safely called to return the size. + */ +bool SourceTextureImage:: +get_size() { + if (!_size_known) { + return read_header(); + } + return true; +} + +/** + * Reads the actual image header to determine the image properties, like its + * size. Returns true if the image header is successfully read (or if has + * previously been successfully read this session), false otherwise. After + * this call returns true, get_x_size() etc. may be safely called to return + * the newly determined size. + */ +bool SourceTextureImage:: +read_header() { + if (_read_header) { + return _successfully_read_header; + } + + _read_header = true; + _successfully_read_header = false; + + PNMImageHeader header; + if (!header.read_header(_filename)) { + nout << "Warning: cannot read texture " + << FilenameUnifier::make_user_filename(_filename) << "\n"; + return false; + } + + set_header(header); + + return true; +} + +/** + * Sets the header information associated with this image, as if it were + * loaded from the disk. + */ +void SourceTextureImage:: +set_header(const PNMImageHeader &header) { + _x_size = header.get_x_size(); + _y_size = header.get_y_size(); + int num_channels = header.get_num_channels(); + + if (!_alpha_filename.empty() && _alpha_filename.exists()) { + // Assume if we have an alpha filename, that we have an additional alpha + // channel. + if (num_channels == 1 || num_channels == 3) { + num_channels++; + } + } + _properties.set_num_channels(num_channels); + + _size_known = true; + _successfully_read_header = true; +} + + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void SourceTextureImage:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void SourceTextureImage:: +write_datagram(BamWriter *writer, Datagram &datagram) { + ImageFile::write_datagram(writer, datagram); + writer->write_pointer(datagram, _texture); + + // We don't store _egg_count; instead, we count these up again each session. + + // We don't store _read_header or _successfully_read_header in the Bam file; + // these are transitory and we need to reread the image header for each + // session (in case the image files change between sessions). +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int SourceTextureImage:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = ImageFile::complete_pointers(p_list, manager); + + DCAST_INTO_R(_texture, p_list[pi++], pi); + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *SourceTextureImage:: +make_from_bam(const FactoryParams ¶ms) { + SourceTextureImage *me = new SourceTextureImage; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void SourceTextureImage:: +fillin(DatagramIterator &scan, BamReader *manager) { + ImageFile::fillin(scan, manager); + manager->read_pointer(scan); // _texture +} diff --git a/pandatool/src/palettizer/sourceTextureImage.h b/pandatool/src/palettizer/sourceTextureImage.h new file mode 100644 index 00000000..6c803a73 --- /dev/null +++ b/pandatool/src/palettizer/sourceTextureImage.h @@ -0,0 +1,85 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file sourceTextureImage.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef SOURCETEXTUREIMAGE_H +#define SOURCETEXTUREIMAGE_H + +#include "pandatoolbase.h" + +#include "imageFile.h" + +class TextureImage; +class PNMImageHeader; + +/** + * This is a texture image reference as it appears in an egg file: the source + * image of the texture. + */ +class SourceTextureImage : public ImageFile { +private: + SourceTextureImage(); + +public: + SourceTextureImage(TextureImage *texture, const Filename &filename, + const Filename &alpha_filename, int alpha_file_channel); + + TextureImage *get_texture() const; + + void increment_egg_count(); + int get_egg_count() const; + + bool get_size(); + bool read_header(); + void set_header(const PNMImageHeader &header); + +private: + TextureImage *_texture; + int _egg_count; + bool _read_header; + bool _successfully_read_header; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ImageFile::init_type(); + register_type(_type_handle, "SourceTextureImage", + ImageFile::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream & +operator << (std::ostream &out, const SourceTextureImage &source) { + source.output_filename(out); + return out; +} + +#endif diff --git a/pandatool/src/palettizer/textureImage.cxx b/pandatool/src/palettizer/textureImage.cxx new file mode 100644 index 00000000..8eddd6bb --- /dev/null +++ b/pandatool/src/palettizer/textureImage.cxx @@ -0,0 +1,1396 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureImage.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "textureImage.h" +#include "sourceTextureImage.h" +#include "destTextureImage.h" +#include "eggFile.h" +#include "paletteGroup.h" +#include "paletteImage.h" +#include "texturePlacement.h" +#include "filenameUnifier.h" +#include "string_utils.h" +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "pnmFileType.h" +#include "indirectCompareNames.h" +#include "pvector.h" + +#include + +using std::string; + +TypeHandle TextureImage::_type_handle; + +/** + * + */ +TextureImage:: +TextureImage() { + _preferred_source = nullptr; + _read_source_image = false; + _allow_release_source_image = true; + _is_surprise = true; + _ever_read_image = false; + _forced_grayscale = false; + _alpha_bits = 0; + _mid_pixel_ratio = 0.0; + _is_cutout = false; + _alpha_mode = EggRenderMode::AM_unspecified; + _txa_wrap_u = EggTexture::WM_unspecified; + _txa_wrap_v = EggTexture::WM_unspecified; + _texture_named = false; + _got_txa_file = false; +} + +/** + * Records that a particular egg file references this texture. This is + * essential to know when deciding how to assign the TextureImage to the + * various PaletteGroups. + */ +void TextureImage:: +note_egg_file(EggFile *egg_file) { + nassertv(!egg_file->get_complete_groups().empty()); + _egg_files.insert(egg_file); +} + +/** + * Assigns the texture to all of the PaletteGroups the various egg files that + * use it need. Attempts to choose the minimum set of PaletteGroups that + * satisfies all of the egg files. + */ +void TextureImage:: +assign_groups() { + if (_egg_files.empty()) { + // If we're not referenced by any egg files any more, assign us to no + // groups. + PaletteGroups empty; + assign_to_groups(empty); + return; + } + + PaletteGroups definitely_in; + + // First, we need to eliminate from consideration all the egg files that are + // already taken care of by the user's explicit group assignments for this + // texture. + WorkingEggs needed_eggs; + + if (_explicitly_assigned_groups.empty()) { + // If we have no explicit group assignments, we must consider all the egg + // files. + std::copy(_egg_files.begin(), _egg_files.end(), std::back_inserter(needed_eggs)); + + } else { + // Otherwise, we only need to consider the egg files that don't have any + // groups in common with our explicit assignments. + + EggFiles::const_iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + PaletteGroups intersect; + intersect.make_intersection(_explicitly_assigned_groups, (*ei)->get_complete_groups()); + if (!intersect.empty()) { + // This egg file is satisfied by one of the texture's explicit + // assignments. + + // We must use at least one of the explicitly-assigned groups that + // satisfied the egg file. We don't need to use all of them, however, + // and we choose the first one arbitrarily. + definitely_in.insert(*intersect.begin()); + + } else { + // This egg file was not satisfied by any of the texture's explicit + // assignments. Therefore, we'll need to choose some additional group + // to assign the texture to, to make the egg file happy. Defer this a + // bit. + needed_eggs.push_back(*ei); + } + } + } + + while (!needed_eggs.empty()) { + // We need to know the complete set of groups that we need to consider + // adding the texture to. This is the union of all the egg files' + // requested groups. + PaletteGroups total; + WorkingEggs::const_iterator ei; + for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) { + total.make_union(total, (*ei)->get_complete_groups()); + } + + // We don't count the "null" group for texture assignment. + total.remove_null(); + if (total.empty()) { + break; + } + + // Now, find the group that will satisfy the most egg files. If two + // groups satisfy the same number of egg files, choose (a) the most + // specific one, i.e. with the lowest dirname_level, or the lowest + // dependency_level if the dirname_levels are equal, and (b) the one that + // has the fewest egg files sharing it. + PaletteGroups::iterator gi = total.begin(); + PaletteGroup *best = (*gi); + int best_egg_count = compute_egg_count(best, needed_eggs); + ++gi; + while (gi != total.end()) { + PaletteGroup *group = (*gi); + + // Do we prefer this group to our current 'best'? + bool prefer_group = false; + int group_egg_count = compute_egg_count(group, needed_eggs); + if (group_egg_count != best_egg_count) { + prefer_group = (group_egg_count > best_egg_count); + + } else { + prefer_group = group->is_preferred_over(*best); + } + + if (prefer_group) { + best = group; + best_egg_count = group_egg_count; + } + ++gi; + } + + // Okay, now we've picked the best group. Eliminate all the eggs from + // consideration that are satisfied by this group, and repeat. + definitely_in.insert(best); + + WorkingEggs next_needed_eggs; + for (ei = needed_eggs.begin(); ei != needed_eggs.end(); ++ei) { + if ((*ei)->get_complete_groups().count(best) == 0) { + // This one wasn't eliminated. + next_needed_eggs.push_back(*ei); + } + } + needed_eggs.swap(next_needed_eggs); + } + + // Finally, now that we've computed the set of groups we need to assign the + // texture to, we need to reconcile this with the set of groups we've + // assigned the texture to previously. + assign_to_groups(definitely_in); +} + +/** + * Once assign_groups() has been called, this returns the actual set of groups + * the TextureImage has been assigned to. + */ +const PaletteGroups &TextureImage:: +get_groups() const { + return _actual_assigned_groups; +} + +/** + * Gets the TexturePlacement object which represents the assignment of this + * texture to the indicated group. If the texture has not been assigned to + * the indicated group, returns NULL. + */ +TexturePlacement *TextureImage:: +get_placement(PaletteGroup *group) const { + Placement::const_iterator pi; + pi = _placement.find(group); + if (pi == _placement.end()) { + return nullptr; + } + + return (*pi).second; +} + +/** + * Removes the texture from any PaletteImages it is assigned to, but does not + * remove it from the groups. It will be re-placed within each group when + * PaletteGroup::place_all() is called. + */ +void TextureImage:: +force_replace() { + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + (*pi).second->force_replace(); + } +} + +/** + * Marks all the egg files that reference this texture stale. Should be + * called only when the texture properties change in some catastrophic way + * that will require every egg file referencing it to be regenerated, even if + * it is not palettized. + */ +void TextureImage:: +mark_eggs_stale() { + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + (*pi).second->mark_eggs_stale(); + } +} + +/** + * Indicates that this particular texture has been named by the user for + * processing this session, normally by listing an egg file on the command + * line that references it. + */ +void TextureImage:: +mark_texture_named() { + _texture_named = true; +} + +/** + * Returns true if this particular texture has been named by the user for + * procession this session, for instance by listing an egg file on the command + * line that references it. + */ +bool TextureImage:: +is_texture_named() const { + return _texture_named; +} + +/** + * Updates any internal state prior to reading the .txa file. + */ +void TextureImage:: +pre_txa_file() { + // Save our current properties, so we can note if they change. + _pre_txa_properties = _properties; + + // Get our properties from the actual image for this texture. It's possible + // the .txa file will update them further. + SourceTextureImage *source = get_preferred_source(); + if (source != nullptr) { + _properties = source->get_properties(); + } + + _pre_txa_alpha_mode = _alpha_mode; + _alpha_mode = EggRenderMode::AM_unspecified; + + _request.pre_txa_file(); + _is_surprise = true; +} + +/** + * Once the .txa file has been read and the TextureImage matched against it, + * considers applying the requested size change. Updates the TextureImage's + * size with the size the texture ought to be, if this can be determined. + */ +void TextureImage:: +post_txa_file() { + _got_txa_file = true; + + // First, get the actual size of the texture. + SourceTextureImage *source = get_preferred_source(); + if (source != nullptr) { + if (source->get_size()) { + _size_known = true; + _x_size = source->get_x_size(); + _y_size = source->get_y_size(); + _properties.set_num_channels(source->get_num_channels()); + } + } + + // Now update this with a particularly requested size. + if (_request._got_size) { + _size_known = true; + _x_size = _request._x_size; + _y_size = _request._y_size; + } + + if (_txa_wrap_u != _request._wrap_u || + _txa_wrap_v != _request._wrap_v) { + _txa_wrap_u = _request._wrap_u; + _txa_wrap_v = _request._wrap_v; + + // If the explicit wrap mode changes, we may need to regenerate the egg + // files, andor refill the palettes. + mark_eggs_stale(); + + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + TexturePlacement *placement = (*pi).second; + placement->mark_unfilled(); + } + } + + if (_properties.has_num_channels() && !_request._keep_format) { + int num_channels = _properties.get_num_channels(); + // Examine the image to determine if we can downgrade the number of + // channels, for instance from color to grayscale. + if (num_channels == 3 || num_channels == 4) { + consider_grayscale(); + } + + // Also consider the alpha properties, and whether we should downgrade + // from alpha to non-alpha. + if (num_channels == 2 || num_channels == 4) { + consider_alpha(); + } + } + + // However, if we got an explicit request for channels, honor that. + if (_request._got_num_channels) { + _properties.set_num_channels(_request._num_channels); + } + + _properties._generic_format = _request._generic_format; + _properties._keep_format = _request._keep_format; + + if (_request._format != EggTexture::F_unspecified) { + _properties._format = _request._format; + _properties._force_format = _request._force_format; + } + + if (_request._minfilter != EggTexture::FT_unspecified) { + _properties._minfilter = _request._minfilter; + } + if (_request._magfilter != EggTexture::FT_unspecified) { + _properties._magfilter = _request._magfilter; + } + + _properties._anisotropic_degree = _request._anisotropic_degree; + + _properties._srgb = _request._srgb; + + if (_properties._color_type == nullptr) { + _properties._color_type = _request._properties._color_type; + _properties._alpha_type = _request._properties._alpha_type; + } + + // Finally, make sure our properties are fully defined. + _properties.fully_define(); + + // Now, if our properties have changed in all that from our previous + // session, we need to re-place ourself in all palette groups. + if (_properties != _pre_txa_properties) { + force_replace(); + + // The above will mark the egg files stale when the texture is palettized + // (since the UV's will certainly need to be recomputed), but sometimes we + // need to mark the egg files stale even when the texture is not + // palettized (if a critical property has changed). The following + // accomplishes this: + if (!_properties.egg_properties_match(_pre_txa_properties)) { + mark_eggs_stale(); + } + } + + // The alpha mode isn't stored in the properties, because it doesn't affect + // which textures may be associated into a common palette. + if (_request._alpha_mode != EggRenderMode::AM_unspecified) { + _alpha_mode = _request._alpha_mode; + } + + // On the other hand, if we don't have an alpha channel, we shouldn't have + // an alpha mode. + if (_properties.has_num_channels()) { + int num_channels = _properties.get_num_channels(); + if (num_channels == 1 || num_channels == 3) { + _alpha_mode = EggRenderMode::AM_unspecified; + } + } + + // If we've changed the alpha mode, we should also mark the eggs stale. + if (_pre_txa_alpha_mode != _alpha_mode) { + mark_eggs_stale(); + } +} + +/** + * Returns true if this TextureImage has been looked up in the .txa file this + * session, false otherwise. + */ +bool TextureImage:: +got_txa_file() const { + return _got_txa_file; +} + +/** + * Calls determine_size() on each TexturePlacement for the texture, to ensure + * that each TexturePlacement is still requesting the best possible size for + * the texture. + */ +void TextureImage:: +determine_placement_size() { + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + TexturePlacement *placement = (*pi).second; + placement->determine_size(); + } +} + +/** + * Returns true if the user specifically requested to omit this texture via + * the "omit" keyword in the .txa file, or false otherwise. + */ +bool TextureImage:: +get_omit() const { + return _request._omit; +} + +/** + * Returns the appropriate coverage threshold for this texture. This is + * either the Palettizer::_coverage_threshold parameter, given globally via + * -r, or a particular value for this texture as supplied by the "coverage" + * keyword in the .txa file. + */ +double TextureImage:: +get_coverage_threshold() const { + return _request._coverage_threshold; +} + +/** + * Returns the appropriate margin for this texture. This is either the + * Palettizer::_margin parameter, or a particular value for this texture as + * supplied by the "margin" keyword in the .txa file. + */ +int TextureImage:: +get_margin() const { + return _request._margin; +} + +/** + * Returns true if this particular texture is a 'surprise', i.e. it wasn't + * matched by a line in the .txa file that didn't include the keyword 'cont'. + */ +bool TextureImage:: +is_surprise() const { + if (_placement.empty()) { + // A texture that is not actually placed anywhere is not considered a + // surprise. + return false; + } + + return _is_surprise; +} + +/** + * Returns true if this particular texture has been placed somewhere, + * anywhere, or false if it is not used. + */ +bool TextureImage:: +is_used() const { + return !_placement.empty(); +} + +/** + * Returns the alpha mode that should be used to render objects with this + * texture, as specified by the user or as determined from examining the + * texture's alpha channel. + */ +EggRenderMode::AlphaMode TextureImage:: +get_alpha_mode() const { + return _alpha_mode; +} + +/** + * Returns the wrap mode specified in the u direction in the txa file, or + * WM_unspecified. + */ +EggTexture::WrapMode TextureImage:: +get_txa_wrap_u() const { + return _txa_wrap_u; +} + +/** + * Returns the wrap mode specified in the v direction in the txa file, or + * WM_unspecified. + */ +EggTexture::WrapMode TextureImage:: +get_txa_wrap_v() const { + return _txa_wrap_v; +} + + +/** + * Returns the SourceTextureImage corresponding to the given filename(s). If + * the given filename has never been used as a SourceTexture for this + * particular texture, creates a new SourceTextureImage and returns that. + */ +SourceTextureImage *TextureImage:: +get_source(const Filename &filename, const Filename &alpha_filename, + int alpha_file_channel) { + string key = get_source_key(filename, alpha_filename, alpha_file_channel); + + Sources::iterator si; + si = _sources.find(key); + if (si != _sources.end()) { + return (*si).second; + } + + SourceTextureImage *source = + new SourceTextureImage(this, filename, alpha_filename, alpha_file_channel); + _sources.insert(Sources::value_type(key, source)); + + // Clear out the preferred source image to force us to rederive this next + // time someone asks. + _preferred_source = nullptr; + _read_source_image = false; + + return source; +} + +/** + * Determines the preferred source image for examining size and reading + * pixels, etc. This is the largest and most recent of all the available + * source images. + */ +SourceTextureImage *TextureImage:: +get_preferred_source() { + if (_preferred_source != nullptr) { + return _preferred_source; + } + + // Now examine all of the various source images available to us and pick the + // most suitable. We base this on the following criteria: + + // (1) A suitable source image must be referenced by at least one egg file, + // unless no source images are referenced by any egg file. + + // (2) A larger source image is preferable to a smaller one. + + // (3) Given two source images of the same size, the more recent one is + // preferable. + + // Are any source images referenced by an egg file? + + bool any_referenced = false; + Sources::iterator si; + for (si = _sources.begin(); si != _sources.end() && !any_referenced; ++si) { + SourceTextureImage *source = (*si).second; + if (source->get_egg_count() > 0) { + any_referenced = true; + } + } + + SourceTextureImage *best = nullptr; + int best_size = 0; + + for (si = _sources.begin(); si != _sources.end(); ++si) { + SourceTextureImage *source = (*si).second; + + if (source->get_egg_count() > 0 || !any_referenced) { + // Rule (1) passes. + + if (source->exists() && source->get_size()) { + int source_size = source->get_x_size() * source->get_y_size(); + if (best == nullptr) { + best = source; + best_size = source_size; + + } else if (source_size > best_size) { + // Rule (2) passes. + best = source; + best_size = source_size; + + } else if (source_size == best_size && + source->get_filename().compare_timestamps(best->get_filename()) > 0) { + // Rule (3) passes. + best = source; + best_size = source_size; + } + } + } + } + + if (best == nullptr && !_sources.empty()) { + // If we didn't pick any that pass, it must be that all of them are + // unreadable. In this case, it really doesn't matter which one we pick, + // but we should at least pick one that has an egg reference, if any of + // them do. + if (any_referenced) { + for (si = _sources.begin(); + si != _sources.end() && best == nullptr; + ++si) { + SourceTextureImage *source = (*si).second; + if (source->get_egg_count() > 0) { + best = source; + } + } + } else { + best = (*_sources.begin()).second; + } + } + + _preferred_source = best; + return _preferred_source; +} + +/** + * Calls clear_basic_properties() on each source texture image used by this + * texture, to reset the properties in preparation for re-applying them from + * the set of all known egg files. + */ +void TextureImage:: +clear_source_basic_properties() { + Sources::iterator si; + for (si = _sources.begin(); si != _sources.end(); ++si) { + SourceTextureImage *source = (*si).second; + source->clear_basic_properties(); + } +} + +/** + * Copies the texture to whichever destination directories are appropriate for + * the groups in which it has been unplaced. Also removes the old filenames + * for previous sessions where it was unplaced, but is no longer. + * + * If redo_all is true, this recopies the texture whether it needed to or not. + */ +void TextureImage:: +copy_unplaced(bool redo_all) { + // First, we need to build up the set of DestTextureImages that represents + // the files we need to generate. + Dests generate; + + // Go through all the TexturePlacements and note the ones for which we're + // unplaced. We check get_omit_reason() and not is_placed(), because we + // want to consider solitary images to be unplaced in this case. + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + TexturePlacement *placement = (*pi).second; + if (placement->get_omit_reason() != OR_none && + placement->get_omit_reason() != OR_unknown) { + DestTextureImage *dest = new DestTextureImage(placement); + Filename filename = dest->get_filename(); + FilenameUnifier::make_canonical(filename); + + std::pair insert_result = generate.insert + (Dests::value_type(filename, dest)); + if (!insert_result.second) { + // At least two DestTextureImages map to the same filename, no sweat. + delete dest; + dest = (*insert_result.first).second; + } + + placement->set_dest(dest); + + } else { + placement->set_dest(nullptr); + } + } + + if (redo_all) { + // If we're redoing everything, we remove everything first and then recopy + // it again. + Dests empty; + remove_old_dests(empty, _dests); + copy_new_dests(generate, empty); + + } else { + // Otherwise, we only remove and recopy the things that changed between + // this time and last time. + remove_old_dests(generate, _dests); + copy_new_dests(generate, _dests); + } + + // Clean up the old set. + Dests::iterator di; + for (di = _dests.begin(); di != _dests.end(); ++di) { + delete (*di).second; + } + + _dests.swap(generate); +} + +/** + * Reads in the original image, if it has not already been read, and returns + * it. + */ +const PNMImage &TextureImage:: +read_source_image() { + if (!_read_source_image) { + SourceTextureImage *source = get_preferred_source(); + if (source != nullptr) { + source->read(_source_image); + } + _read_source_image = true; + _allow_release_source_image = true; + _ever_read_image = true; + } + + return _source_image; +} + +/** + * Frees the memory that was allocated by a previous call to + * read_source_image(). The next time read_source_image() is called, it will + * have to read the disk again. + */ +void TextureImage:: +release_source_image() { + if (_read_source_image && _allow_release_source_image) { + _source_image.clear(); + _read_source_image = false; + } +} + +/** + * Accepts the indicated source image as if it had been read from disk. This + * image is copied into the structure, and will be returned by future calls to + * read_source_image(). + */ +void TextureImage:: +set_source_image(const PNMImage &image) { + _source_image = image; + _allow_release_source_image = false; + _read_source_image = true; + _ever_read_image = true; +} + +/** + * Causes the header part of the image to be reread, usually to confirm that + * its image properties (size, number of channels, etc.) haven't changed. + */ +void TextureImage:: +read_header() { + if (!_read_source_image) { + SourceTextureImage *source = get_preferred_source(); + if (source != nullptr) { + source->read_header(); + } + } +} + +/** + * Returns true if the source image is newer than the indicated file, false + * otherwise. If the image has already been read, this always returns false. + */ +bool TextureImage:: +is_newer_than(const Filename &reference_filename) { + if (!_read_source_image) { + SourceTextureImage *source = get_preferred_source(); + if (source != nullptr) { + const Filename &source_filename = source->get_filename(); + return source_filename.compare_timestamps(reference_filename) >= 0; + } + } + + return false; +} + +/** + * Writes the list of source pathnames that might contribute to this texture + * to the indicated output stream, one per line. + */ +void TextureImage:: +write_source_pathnames(std::ostream &out, int indent_level) const { + Sources::const_iterator si; + for (si = _sources.begin(); si != _sources.end(); ++si) { + SourceTextureImage *source = (*si).second; + + if (source->get_egg_count() > 0) { + indent(out, indent_level); + source->output_filename(out); + if (!source->is_size_known()) { + out << " (unknown size)"; + + } else { + out << " " << source->get_x_size() << " " + << source->get_y_size(); + + if (source->get_properties().has_num_channels()) { + out << " " << source->get_properties().get_num_channels(); + } + } + out << "\n"; + } + } + + if (_is_cutout) { + indent(out, indent_level) + << "Cutout image (ratio " << (PN_stdfloat)_mid_pixel_ratio << ")\n"; + } + + // Now write out the group assignments. + if (!_egg_files.empty()) { + // Sort the egg files into order by name for output. + pvector egg_vector; + egg_vector.reserve(_egg_files.size()); + EggFiles::const_iterator ei; + for (ei = _egg_files.begin(); ei != _egg_files.end(); ++ei) { + egg_vector.push_back(*ei); + } + sort(egg_vector.begin(), egg_vector.end(), + IndirectCompareNames()); + + indent(out, indent_level) + << "Used by:\n"; + pvector::const_iterator evi; + for (evi = egg_vector.begin(); evi != egg_vector.end(); ++evi) { + EggFile *egg = (*evi); + indent(out, indent_level + 2) + << egg->get_name() << " ("; + if (egg->get_explicit_groups().empty()) { + out << *egg->get_default_group(); + } else { + out << egg->get_explicit_groups(); + } + out << ")\n"; + } + } + if (!_explicitly_assigned_groups.empty()) { + indent(out, indent_level) + << "Explicitly assigned to " << _explicitly_assigned_groups << " in .txa\n"; + } + + if (_placement.empty()) { + indent(out, indent_level) + << "Not used.\n"; + } else { + indent(out, indent_level) + << "Assigned to " << _actual_assigned_groups << "\n"; + } +} + +/** + * Writes the information about the texture's size and placement. + */ +void TextureImage:: +write_scale_info(std::ostream &out, int indent_level) { + SourceTextureImage *source = get_preferred_source(); + indent(out, indent_level) << get_name(); + + // Write the list of groups we're placed in. + if (_placement.empty()) { + out << " (not used)"; + } else { + Placement::const_iterator pi; + pi = _placement.begin(); + out << " (" << (*pi).second->get_group()->get_name(); + ++pi; + while (pi != _placement.end()) { + out << " " << (*pi).second->get_group()->get_name(); + ++pi; + } + out << ")"; + } + + out << " orig "; + + if (source == nullptr || + !source->is_size_known()) { + out << "unknown"; + } else { + out << source->get_x_size() << " " << source->get_y_size() + << " " << source->get_num_channels(); + } + + if (!_placement.empty() && is_size_known()) { + out << " new " << get_x_size() << " " << get_y_size() + << " " << get_num_channels(); + + if (source != nullptr && + source->is_size_known()) { + double scale = + 100.0 * (((double)get_x_size() / (double)source->get_x_size()) + + ((double)get_y_size() / (double)source->get_y_size())) / 2.0; + out << " scale " << scale << "%"; + } + } + out << "\n"; + + // Also cross-reference the placed and unplaced information. + Placement::iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + TexturePlacement *placement = (*pi).second; + if (placement->get_omit_reason() == OR_none) { + PaletteImage *image = placement->get_image(); + nassertv(image != nullptr); + indent(out, indent_level + 2) + << "placed on " + << FilenameUnifier::make_user_filename(image->get_filename()) + << "\n"; + + } else if (placement->get_omit_reason() == OR_unknown) { + indent(out, indent_level + 2) + << "not placed because unknown.\n"; + + } else { + DestTextureImage *image = placement->get_dest(); + nassertv(image != nullptr); + indent(out, indent_level + 2) + << "copied to " + << FilenameUnifier::make_user_filename(image->get_filename()); + if (image->is_size_known() && is_size_known() && + (image->get_x_size() != get_x_size() || + image->get_y_size() != get_y_size())) { + out << " at size " << image->get_x_size() << " " + << image->get_y_size(); + if (source != nullptr && + source->is_size_known()) { + double scale = + 100.0 * (((double)image->get_x_size() / (double)source->get_x_size()) + + ((double)image->get_y_size() / (double)source->get_y_size())) / 2.0; + out << " scale " << scale << "%"; + } + } + out << "\n"; + } + } +} + +/** + * Counts the number of egg files in the indicated set that will be satisfied + * if a texture is assigned to the indicated group. + */ +int TextureImage:: +compute_egg_count(PaletteGroup *group, + const TextureImage::WorkingEggs &egg_files) { + int count = 0; + + WorkingEggs::const_iterator ei; + for (ei = egg_files.begin(); ei != egg_files.end(); ++ei) { + if ((*ei)->get_complete_groups().count(group) != 0) { + count++; + } + } + + return count; +} + +/** + * Assigns the texture to the indicated set of groups. If the texture was + * previously assigned to any of these groups, keeps the same TexturePlacement + * object for the assignment; at the same time, deletes any TexturePlacement + * objects that represent groups we are no longer assigned to. + */ +void TextureImage:: +assign_to_groups(const PaletteGroups &groups) { + PaletteGroups::const_iterator gi; + Placement::const_iterator pi; + + Placement new_placement; + + gi = groups.begin(); + pi = _placement.begin(); + + while (gi != groups.end() && pi != _placement.end()) { + PaletteGroup *a = (*gi); + PaletteGroup *b = (*pi).first; + + if (a < b) { + // Here's a group we're now assigned to that we weren't assigned to + // previously. + TexturePlacement *place = a->prepare(this); + new_placement.insert + (new_placement.end(), Placement::value_type(a, place)); + ++gi; + + } else if (b < a) { + // Here's a group we're no longer assigned to. + TexturePlacement *place = (*pi).second; + delete place; + ++pi; + + } else { // b == a + // Here's a group we're still assigned to. + TexturePlacement *place = (*pi).second; + new_placement.insert + (new_placement.end(), Placement::value_type(a, place)); + ++gi; + ++pi; + } + } + + while (gi != groups.end()) { + // Here's a group we're now assigned to that we weren't assigned to + // previously. + PaletteGroup *a = (*gi); + TexturePlacement *place = a->prepare(this); + new_placement.insert + (new_placement.end(), Placement::value_type(a, place)); + ++gi; + } + + while (pi != _placement.end()) { + // Here's a group we're no longer assigned to. + TexturePlacement *place = (*pi).second; + delete place; + ++pi; + } + + _placement.swap(new_placement); + _actual_assigned_groups = groups; +} + +/** + * Examines the actual contents of the image to determine if it should maybe + * be considered a grayscale image (even though it has separate rgb + * components). + */ +void TextureImage:: +consider_grayscale() { + // Since this isn't likely to change for a particular texture after its + // creation, we save a bit of time by not performing this check unless this + // is the first time we've ever seen this texture. This will save us from + // having to load the texture images each time we look at them. On the + // other hand, if we've already loaded up the image, then go ahead. + if (!_read_source_image && _ever_read_image) { + if (_forced_grayscale) { + _properties.force_grayscale(); + } + return; + } + + const PNMImage &source = read_source_image(); + if (!source.is_valid()) { + return; + } + + for (int y = 0; y < source.get_y_size(); y++) { + for (int x = 0; x < source.get_x_size(); x++) { + const xel &v = source.get_xel_val(x, y); + if (PPM_GETR(v) != PPM_GETG(v) || PPM_GETR(v) != PPM_GETB(v)) { + // Here's a colored pixel. We can't go grayscale. + _forced_grayscale = false; + return; + } + } + } + + // All pixels in the image were grayscale! + _properties.force_grayscale(); + _forced_grayscale = true; +} + +/** + * Examines the actual contents of the image to determine what alpha + * properties it has. + */ +void TextureImage:: +consider_alpha() { + // As above, we don't bother doing this if we've already done this in a + // previous session. + + // _alpha_bits == -1 indicates we have read an older textures.boo file that + // didn't define these bits. + if (_read_source_image || !_ever_read_image || _alpha_bits == -1) { + _alpha_bits = 0; + int num_mid_pixels = 0; + + const PNMImage &source = read_source_image(); + if (source.is_valid() && source.has_alpha()) { + xelval maxval = source.get_maxval(); + for (int y = 0; y < source.get_y_size(); y++) { + for (int x = 0; x < source.get_x_size(); x++) { + xelval alpha_val = source.get_alpha_val(x, y); + if (alpha_val == 0) { + _alpha_bits |= AB_zero; + } else if (alpha_val == maxval) { + _alpha_bits |= AB_one; + } else { + _alpha_bits |= AB_mid; + ++num_mid_pixels; + } + } + } + } + + int num_pixels = source.get_x_size() * source.get_y_size(); + _mid_pixel_ratio = 0.0; + if (num_pixels != 0) { + _mid_pixel_ratio = (double)num_mid_pixels / (double)num_pixels; + } + } + + _is_cutout = false; + + if (_alpha_bits != 0) { + if (_alpha_bits == AB_one) { + // All alpha pixels are white; drop the alpha channel. + _properties.force_nonalpha(); + + } else if (_alpha_bits == AB_zero) { + // All alpha pixels are invisible; this is probably a mistake. Drop the + // alpha channel and complain. + _properties.force_nonalpha(); + if (_read_source_image) { + nout << *this << " has an all-zero alpha channel; dropping alpha.\n"; + } + + } else if (_alpha_mode == EggRenderMode::AM_unspecified) { + // Consider fiddling with the alpha mode, if the user hasn't specified a + // particular alpha mode in the txa file. + if ((_alpha_bits & AB_mid) == 0) { + // No middle range bits: a binary alpha image. + _alpha_mode = EggRenderMode::AM_binary; + + } else if ((_alpha_bits & AB_one) != 0 && _mid_pixel_ratio < pal->_cutout_ratio) { + // At least some opaque bits, and relatively few middle range bits: a + // cutout image. + _alpha_mode = pal->_cutout_mode; + _is_cutout = true; + + } else { + // No opaque bits; just use regular alpha blending. + _alpha_mode = EggRenderMode::AM_blend; + } + } + } +} + +/** + * Removes all of the filenames named in b that are not also named in a. + */ +void TextureImage:: +remove_old_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) { + Dests::const_iterator ai = a.begin(); + Dests::const_iterator bi = b.begin(); + + while (ai != a.end() && bi != b.end()) { + const string &astr = (*ai).first; + const string &bstr = (*bi).first; + + if (astr < bstr) { + // Here's a filename in a, not in b. + ++ai; + + } else if (bstr < astr) { + // Here's a filename in b, not in a. + (*bi).second->unlink(); + ++bi; + + } else { // bstr == astr + // Here's a filename in both a and b. + ++ai; + ++bi; + } + } + + while (bi != b.end()) { + // Here's a filename in b, not in a. + (*bi).second->unlink(); + ++bi; + } + + while (ai != a.end()) { + ++ai; + } +} + +/** + * Copies a resized texture into each filename named in a that is not also + * listed in b, or whose corresponding listing in b is out of date. + */ +void TextureImage:: +copy_new_dests(const TextureImage::Dests &a, const TextureImage::Dests &b) { + Dests::const_iterator ai = a.begin(); + Dests::const_iterator bi = b.begin(); + + while (ai != a.end() && bi != b.end()) { + const string &astr = (*ai).first; + const string &bstr = (*bi).first; + + if (astr < bstr) { + // Here's a filename in a, not in b. + (*ai).second->copy(this); + ++ai; + + } else if (bstr < astr) { + // Here's a filename in b, not in a. + ++bi; + + } else { // bstr == astr + // Here's a filename in both a and b. + (*ai).second->copy_if_stale((*bi).second, this); + ++ai; + ++bi; + } + } + + while (ai != a.end()) { + // Here's a filename in a, not in b. + (*ai).second->copy(this); + ++ai; + } +} + +/** + * Returns the key that a SourceTextureImage should be stored in, given its + * one or two filenames. + */ +string TextureImage:: +get_source_key(const Filename &filename, const Filename &alpha_filename, + int alpha_file_channel) { + Filename f = FilenameUnifier::make_bam_filename(filename); + Filename a = FilenameUnifier::make_bam_filename(alpha_filename); + + return f.get_fullpath() + ":" + a.get_fullpath() + ":" + + format_string(alpha_file_channel); +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void TextureImage:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void TextureImage:: +write_datagram(BamWriter *writer, Datagram &datagram) { + ImageFile::write_datagram(writer, datagram); + datagram.add_string(get_name()); + + // We don't write out _request; this is re-read from the .txa file each + // time. + + // We don't write out _pre_txa_properties; this is transitional. + + // We don't write out _preferred_source; this is redetermined each session. + + datagram.add_bool(_is_surprise); + datagram.add_bool(_ever_read_image); + datagram.add_bool(_forced_grayscale); + datagram.add_uint8(_alpha_bits); + datagram.add_int16((int)_alpha_mode); + datagram.add_float64(_mid_pixel_ratio); + datagram.add_bool(_is_cutout); + datagram.add_uint8((int)_txa_wrap_u); + datagram.add_uint8((int)_txa_wrap_v); + + // We don't write out _explicitly_assigned_groups; this is re-read from the + // .txa file each time. + + _actual_assigned_groups.write_datagram(writer, datagram); + + // We don't write out _egg_files; this is redetermined each session. + + datagram.add_uint32(_placement.size()); + Placement::const_iterator pi; + for (pi = _placement.begin(); pi != _placement.end(); ++pi) { + writer->write_pointer(datagram, (*pi).first); + writer->write_pointer(datagram, (*pi).second); + } + + datagram.add_uint32(_sources.size()); + Sources::const_iterator si; + for (si = _sources.begin(); si != _sources.end(); ++si) { + writer->write_pointer(datagram, (*si).second); + } + + datagram.add_uint32(_dests.size()); + Dests::const_iterator di; + for (di = _dests.begin(); di != _dests.end(); ++di) { + writer->write_pointer(datagram, (*di).second); + } +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int TextureImage:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = ImageFile::complete_pointers(p_list, manager); + + pi += _actual_assigned_groups.complete_pointers(p_list + pi, manager); + + int i; + for (i = 0; i < _num_placement; i++) { + PaletteGroup *group; + TexturePlacement *placement; + DCAST_INTO_R(group, p_list[pi++], pi); + DCAST_INTO_R(placement, p_list[pi++], pi); + _placement.insert(Placement::value_type(group, placement)); + } + + for (i = 0; i < _num_sources; i++) { + SourceTextureImage *source; + DCAST_INTO_R(source, p_list[pi++], pi); + string key = get_source_key(source->get_filename(), + source->get_alpha_filename(), + source->get_alpha_file_channel()); + + bool inserted = _sources.insert(Sources::value_type(key, source)).second; + if (!inserted) { + nout << "Warning: texture key " << key + << " is nonunique; texture lost.\n"; + } + } + + for (i = 0; i < _num_dests; i++) { + DestTextureImage *dest; + DCAST_INTO_R(dest, p_list[pi++], pi); + bool inserted = _dests.insert(Dests::value_type(dest->get_filename(), dest)).second; + if (!inserted) { + nout << "Warning: dest filename " << dest->get_filename() + << " is nonunique; texture lost.\n"; + } + } + + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *TextureImage:: +make_from_bam(const FactoryParams ¶ms) { + TextureImage *me = new TextureImage; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void TextureImage:: +fillin(DatagramIterator &scan, BamReader *manager) { + ImageFile::fillin(scan, manager); + set_name(scan.get_string()); + + _is_surprise = scan.get_bool(); + _ever_read_image = scan.get_bool(); + _forced_grayscale = scan.get_bool(); + _alpha_bits = scan.get_uint8(); + _alpha_mode = (EggRenderMode::AlphaMode)scan.get_int16(); + if (pal->_read_pi_version >= 16) { + _mid_pixel_ratio = scan.get_float64(); + _is_cutout = scan.get_bool(); + } else { + // Force a re-read of the image if we are upgrading to pi version 16. + _ever_read_image = false; + _mid_pixel_ratio = 0.0; + _is_cutout = false; + } + if (pal->_read_pi_version >= 17) { + _txa_wrap_u = (EggTexture::WrapMode)scan.get_uint8(); + _txa_wrap_v = (EggTexture::WrapMode)scan.get_uint8(); + } + + _actual_assigned_groups.fillin(scan, manager); + + _num_placement = scan.get_uint32(); + manager->read_pointers(scan, _num_placement * 2); + + _num_sources = scan.get_uint32(); + manager->read_pointers(scan, _num_sources); + _num_dests = scan.get_uint32(); + manager->read_pointers(scan, _num_dests); +} diff --git a/pandatool/src/palettizer/textureImage.h b/pandatool/src/palettizer/textureImage.h new file mode 100644 index 00000000..3fd041d6 --- /dev/null +++ b/pandatool/src/palettizer/textureImage.h @@ -0,0 +1,195 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureImage.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef TEXTUREIMAGE_H +#define TEXTUREIMAGE_H + +#include "pandatoolbase.h" + +#include "imageFile.h" +#include "paletteGroups.h" +#include "textureRequest.h" + +#include "namable.h" +#include "filename.h" +#include "pnmImage.h" +#include "eggRenderMode.h" + +#include "pmap.h" +#include "pset.h" + +class SourceTextureImage; +class DestTextureImage; +class TexturePlacement; +class EggFile; + +/** + * This represents a single source texture that is referenced by one or more + * egg files. It may be assigned to multiple PaletteGroups, and thus placed + * on multiple PaletteImages (up to one per PaletteGroup). + * + * Since a TextureImage may be referenced by multiple egg files that are each + * assigned to a different set of groups, it tries to maximize sharing between + * egg files and minimize the number of different PaletteGroups it is assigned + * to. + */ +class TextureImage : public ImageFile, public Namable { +public: + TextureImage(); + + void note_egg_file(EggFile *egg_file); + void assign_groups(); + + const PaletteGroups &get_groups() const; + TexturePlacement *get_placement(PaletteGroup *group) const; + void force_replace(); + void mark_eggs_stale(); + + void mark_texture_named(); + bool is_texture_named() const; + + void pre_txa_file(); + void post_txa_file(); + bool got_txa_file() const; + void determine_placement_size(); + + bool get_omit() const; + double get_coverage_threshold() const; + int get_margin() const; + bool is_surprise() const; + bool is_used() const; + EggRenderMode::AlphaMode get_alpha_mode() const; + + EggTexture::WrapMode get_txa_wrap_u() const; + EggTexture::WrapMode get_txa_wrap_v() const; + + SourceTextureImage *get_source(const Filename &filename, + const Filename &alpha_filename, + int alpha_file_channel); + + SourceTextureImage *get_preferred_source(); + void clear_source_basic_properties(); + + void copy_unplaced(bool redo_all); + + const PNMImage &read_source_image(); + void release_source_image(); + void set_source_image(const PNMImage &image); + void read_header(); + bool is_newer_than(const Filename &reference_filename); + + void write_source_pathnames(std::ostream &out, int indent_level = 0) const; + void write_scale_info(std::ostream &out, int indent_level = 0); + +private: + typedef pset EggFiles; + typedef pvector WorkingEggs; + typedef pmap Sources; + typedef pmap Dests; + + static int compute_egg_count(PaletteGroup *group, + const WorkingEggs &egg_files); + + void assign_to_groups(const PaletteGroups &groups); + void consider_grayscale(); + void consider_alpha(); + + void remove_old_dests(const Dests &a, const Dests &b); + void copy_new_dests(const Dests &a, const Dests &b); + + std::string get_source_key(const Filename &filename, + const Filename &alpha_filename, + int alpha_file_channel); + +private: + TextureRequest _request; + TextureProperties _pre_txa_properties; + EggRenderMode::AlphaMode _pre_txa_alpha_mode; + SourceTextureImage *_preferred_source; + bool _is_surprise; + + bool _ever_read_image; + bool _forced_grayscale; + + enum AlphaBits { + // consider_alpha() sets alpha_bits to the union of all of these pixel + // values that might be found in the alpha channel. + AB_one = 0x01, + AB_mid = 0x02, + AB_zero = 0x04, + AB_all = 0x07 // == AB_zero | AB_mid | AB_one + }; + int _alpha_bits; + double _mid_pixel_ratio; + bool _is_cutout; + EggRenderMode::AlphaMode _alpha_mode; + EggTexture::WrapMode _txa_wrap_u, _txa_wrap_v; + + PaletteGroups _explicitly_assigned_groups; + PaletteGroups _actual_assigned_groups; + + EggFiles _egg_files; + + typedef pmap Placement; + Placement _placement; + + Sources _sources; + Dests _dests; + + bool _read_source_image; + bool _allow_release_source_image; + PNMImage _source_image; + bool _texture_named; + bool _got_txa_file; + + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // These values are only filled in while reading from the bam file; don't + // use them otherwise. + int _num_placement; + int _num_sources; + int _num_dests; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ImageFile::init_type(); + Namable::init_type(); + register_type(_type_handle, "TextureImage", + ImageFile::get_class_type(), + Namable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; + + friend class TxaLine; +}; + +#endif diff --git a/pandatool/src/palettizer/textureMemoryCounter.cxx b/pandatool/src/palettizer/textureMemoryCounter.cxx new file mode 100644 index 00000000..91281670 --- /dev/null +++ b/pandatool/src/palettizer/textureMemoryCounter.cxx @@ -0,0 +1,250 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureMemoryCounter.cxx + * @author drose + * @date 2000-12-19 + */ + +#include "textureMemoryCounter.h" +#include "paletteImage.h" +#include "textureImage.h" +#include "destTextureImage.h" +#include "omitReason.h" +#include "texturePlacement.h" + +#include "indent.h" +#include + +/** + * + */ +TextureMemoryCounter:: +TextureMemoryCounter() { + reset(); +} + +/** + * Resets the count to zero. + */ +void TextureMemoryCounter:: +reset() { + _num_textures = 0; + _num_unplaced = 0; + _num_placed = 0; + _num_palettes = 0; + + _bytes = 0; + _unused_bytes = 0; + _duplicate_bytes = 0; + _coverage_bytes = 0; + _textures.clear(); + _palettes.clear(); +} + +/** + * Adds the indicated TexturePlacement to the counter. + */ +void TextureMemoryCounter:: +add_placement(TexturePlacement *placement) { + TextureImage *texture = placement->get_texture(); + nassertv(texture != nullptr); + + if (placement->get_omit_reason() == OR_none) { + PaletteImage *image = placement->get_image(); + nassertv(image != nullptr); + add_palette(image); + + int bytes = count_bytes(image, placement->get_placed_x_size(), + placement->get_placed_y_size()); + add_texture(texture, bytes); + _num_placed++; + + } else { + DestTextureImage *dest = placement->get_dest(); + if (dest != nullptr) { + int bytes = count_bytes(dest); + add_texture(texture, bytes); + + _bytes += bytes; + _num_unplaced++; + } + } +} + +/** + * Reports the measured texture memory usage. + */ +void TextureMemoryCounter:: +report(std::ostream &out, int indent_level) { + indent(out, indent_level) + << _num_placed << " of " << _num_textures << " textures appear on " + << _num_palettes << " palette images with " << _num_unplaced + << " unplaced.\n"; + + indent(out, indent_level) + << (_bytes + 512) / 1024 << "k estimated texture memory required.\n"; + + if (_bytes != 0) { + if (_unused_bytes != 0) { + indent(out, indent_level + 2); + format_memory_fraction(out, _unused_bytes, _bytes) + << " is wasted because of unused palette space.\n"; + } + + if (_coverage_bytes > 0) { + indent(out, indent_level + 2); + format_memory_fraction(out, _coverage_bytes, _bytes) + << " is wasted for repeating textures and margins.\n"; + + } else if (_coverage_bytes < 0) { + indent(out, indent_level + 2); + format_memory_fraction(out, -_coverage_bytes, _bytes) + << " is *saved* for palettizing partial textures.\n"; + } + + if (_duplicate_bytes != 0) { + indent(out, indent_level + 2); + format_memory_fraction(out, _duplicate_bytes, _bytes) + << " is wasted because of a texture appearing in multiple groups.\n"; + } + } +} + +/** + * Writes to the indicated ostream an indication of the fraction of the total + * memory usage that is represented by fraction_bytes. + */ +std::ostream &TextureMemoryCounter:: +format_memory_fraction(std::ostream &out, int fraction_bytes, int palette_bytes) { + out << floor(1000.0 * (double)fraction_bytes / (double)palette_bytes + 0.5) / 10.0 + << "% (" << (fraction_bytes + 512) / 1024 << "k)"; + return out; +} + +/** + * Adds the indicated PaletteImage to the count. If this is called twice for + * a given PaletteImage it does nothing. + */ +void TextureMemoryCounter:: +add_palette(PaletteImage *image) { + bool inserted = _palettes.insert(image).second; + if (!inserted) { + // We've already added this palette image. + return; + } + + int bytes = count_bytes(image); + double unused = 1.0 - image->count_utilization(); + double coverage = image->count_coverage(); + + _bytes += bytes; + _unused_bytes += (int)(unused * bytes); + _coverage_bytes += (int)(coverage * bytes); + + _num_palettes++; +} + +/** + * Adds the given TextureImage to the counter. If the texture image has + * already been added, this counts the smaller of the two as duplicate bytes. + */ +void TextureMemoryCounter:: +add_texture(TextureImage *texture, int bytes) { + std::pair result; + result = _textures.insert(Textures::value_type(texture, bytes)); + if (result.second) { + // If it was inserted, no problem--no duplicates. + _num_textures++; + return; + } + + // If it was not inserted, we have a duplicate. + Textures::iterator ti = result.first; + + _duplicate_bytes += std::min(bytes, (*ti).second); + (*ti).second = std::max(bytes, (*ti).second); +} + +/** + * Attempts to estimate the number of bytes the given image file will use in + * texture memory. + */ +int TextureMemoryCounter:: +count_bytes(ImageFile *image) { + return count_bytes(image, image->get_x_size(), image->get_y_size()); +} + +/** + * Attempts to estimate the number of bytes the given image file will use in + * texture memory. + */ +int TextureMemoryCounter:: +count_bytes(ImageFile *image, int x_size, int y_size) { + int pixels = x_size * y_size; + + // Try to guess the number of bytes per pixel this texture will consume in + // texture memory, based on its requested format. This is only a loose + // guess, because this depends of course on the pecularities of the + // particular rendering engine. + int bpp = 0; + switch (image->get_properties()._format) { + case EggTexture::F_rgba12: + bpp = 6; + break; + + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgba8: + bpp = 4; + break; + + case EggTexture::F_rgb: + case EggTexture::F_rgb12: + bpp = 3; + break; + + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + case EggTexture::F_rgb8: + case EggTexture::F_rgb5: + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + bpp = 2; + break; + + case EggTexture::F_rgb332: + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + bpp = 1; + break; + + default: + bpp = image->get_num_channels(); + } + + int bytes = pixels * bpp; + + // If we're mipmapping, it's worth 13 more bytes. + switch (image->get_properties()._minfilter) { + case EggTexture::FT_nearest_mipmap_nearest: + case EggTexture::FT_linear_mipmap_nearest: + case EggTexture::FT_nearest_mipmap_linear: + case EggTexture::FT_linear_mipmap_linear: + bytes = (bytes * 4) / 3; + break; + + default: + break; + } + + return bytes; +} diff --git a/pandatool/src/palettizer/textureMemoryCounter.h b/pandatool/src/palettizer/textureMemoryCounter.h new file mode 100644 index 00000000..49de4747 --- /dev/null +++ b/pandatool/src/palettizer/textureMemoryCounter.h @@ -0,0 +1,67 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureMemoryCounter.h + * @author drose + * @date 2000-12-19 + */ + +#ifndef TEXTUREMEMORYCOUNTER_H +#define TEXTUREMEMORYCOUNTER_H + +#include "pandatoolbase.h" + +class ImageFile; +class PaletteImage; +class TextureImage; +class DestTextureImage; +class TexturePlacement; + +#include "pmap.h" +#include "pset.h" + +/** + * This class is used to gather statistics on texture memory usage, etc. It + * adds up the total texture memory required by a number of image files, and + * reports it at the end. + */ +class TextureMemoryCounter { +public: + TextureMemoryCounter(); + + void reset(); + void add_placement(TexturePlacement *placement); + + void report(std::ostream &out, int indent_level); + +private: + static std::ostream &format_memory_fraction(std::ostream &out, int fraction_bytes, + int palette_bytes); + void add_palette(PaletteImage *image); + void add_texture(TextureImage *texture, int bytes); + int count_bytes(ImageFile *image); + int count_bytes(ImageFile *image, int x_size, int y_size); + + int _num_textures; + int _num_placed; + int _num_unplaced; + int _num_palettes; + + int _bytes; + int _unused_bytes; + int _duplicate_bytes; + int _coverage_bytes; + + typedef pmap Textures; + Textures _textures; + + typedef pset Palettes; + Palettes _palettes; +}; + +#endif diff --git a/pandatool/src/palettizer/texturePlacement.cxx b/pandatool/src/palettizer/texturePlacement.cxx new file mode 100644 index 00000000..b92583b6 --- /dev/null +++ b/pandatool/src/palettizer/texturePlacement.cxx @@ -0,0 +1,1148 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file texturePlacement.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "texturePlacement.h" +#include "textureReference.h" +#include "textureImage.h" +#include "paletteGroup.h" +#include "paletteImage.h" +#include "palettizer.h" +#include "eggFile.h" +#include "destTextureImage.h" + +#include "indent.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "pnmImage.h" + +using std::max; +using std::min; + +TypeHandle TexturePlacement::_type_handle; + +/** + * The default constructor is only for the convenience of the Bam reader. + */ +TexturePlacement:: +TexturePlacement() { + _texture = nullptr; + _group = nullptr; + _image = nullptr; + _dest = nullptr; + _has_uvs = false; + _size_known = false; + _is_filled = true; + _omit_reason = OR_none; +} + +/** + * + */ +TexturePlacement:: +TexturePlacement(TextureImage *texture, PaletteGroup *group) : + _texture(texture), + _group(group) +{ + _omit_reason = OR_working; + + if (!texture->is_size_known()) { + // If we were never able to figure out what size the texture actually is, + // then we can't place the texture on a palette. + _omit_reason = OR_unknown; + } + + _image = nullptr; + _dest = nullptr; + _has_uvs = false; + _size_known = false; + _is_filled = false; +} + +/** + * + */ +TexturePlacement:: +~TexturePlacement() { + // Make sure we tell all our egg references they're not using us any more. + References::iterator ri; + References copy_references = _references; + for (ri = copy_references.begin(); ri != copy_references.end(); ++ri) { + TextureReference *reference = (*ri); + nassertv(reference->get_placement() == this); + reference->clear_placement(); + } + + // And also our group, etc. + _group->unplace(this); +} + +/** + * Returns the name of the texture that this placement represents. + */ +const std::string &TexturePlacement:: +get_name() const { + return _texture->get_name(); +} + +/** + * Returns the texture that this placement represents. + */ +TextureImage *TexturePlacement:: +get_texture() const { + return _texture; +} + +/** + * Returns the grouping properties of the image. + */ +const TextureProperties &TexturePlacement:: +get_properties() const { + return _texture->get_properties(); +} + +/** + * Returns the group that this placement represents. + */ +PaletteGroup *TexturePlacement:: +get_group() const { + return _group; +} + +/** + * Records the fact that a particular egg file is using this particular + * TexturePlacement. + */ +void TexturePlacement:: +add_egg(TextureReference *reference) { + reference->mark_egg_stale(); + + // Turns out that turning these off is a bad idea, because it may make us + // forget the size information halfway through processing. + /* + _has_uvs = false; + _size_known = false; + */ + _references.insert(reference); +} + +/** + * Notes that a particular egg file is no longer using this particular + * TexturePlacement. + */ +void TexturePlacement:: +remove_egg(TextureReference *reference) { + reference->mark_egg_stale(); + /* + _has_uvs = false; + _size_known = false; + */ + _references.erase(reference); +} + +/** + * Marks all the egg files that reference this placement stale. Presumably + * this is called after moving the texture around in the palette or something. + */ +void TexturePlacement:: +mark_eggs_stale() { + References::iterator ri; + for (ri = _references.begin(); ri != _references.end(); ++ri) { + TextureReference *reference = (*ri); + + reference->mark_egg_stale(); + } +} + +/** + * Sets the DestTextureImage that corresponds to this texture as it was copied + * to the install directory. + */ +void TexturePlacement:: +set_dest(DestTextureImage *dest) { + _dest = dest; +} + +/** + * Returns the DestTextureImage that corresponds to this texture as it was + * copied to the install directory. + */ +DestTextureImage *TexturePlacement:: +get_dest() const { + return _dest; +} + +/** + * Attempts to determine the appropriate size of the texture for the given + * placement. This is based on the UV range of the egg files that reference + * the texture. Returns true on success, or false if the texture size cannot + * be determined (e.g. the texture file is unknown). + * + * After this returns true, get_x_size() and get_y_size() may safely be + * called. + */ +bool TexturePlacement:: +determine_size() { + if (!_texture->is_size_known()) { + // Too bad. + force_replace(); + _omit_reason = OR_unknown; + return false; + } + + // This seems to be unnecessary (because of omit_solitary() and + // not_solitary()), and in fact bitches the logic in omit_solitary() and + // not_solitary() so that we call mark_egg_stale() unnecessarily. + /* + if (_omit_reason == OR_solitary) { + // If the texture was previously 'omitted' for being solitary, we give it + // a second chance now. + _omit_reason = OR_none; + } + */ + + // Determine the actual minmax of the UV's in use, as well as whether we + // should wrap or clamp. + _has_uvs = false; + _position._wrap_u = EggTexture::WM_clamp; + _position._wrap_v = EggTexture::WM_clamp; + + LTexCoordd max_uv, min_uv; + + References::iterator ri; + for (ri = _references.begin(); ri != _references.end(); ++ri) { + TextureReference *reference = (*ri); + if (reference->has_uvs()) { + const LTexCoordd &n = reference->get_min_uv(); + const LTexCoordd &x = reference->get_max_uv(); + + if (_has_uvs) { + min_uv.set(min(min_uv[0], n[0]), min(min_uv[1], n[1])); + max_uv.set(max(max_uv[0], x[0]), max(max_uv[1], x[1])); + } else { + min_uv = n; + max_uv = x; + _has_uvs = true; + } + } + + // If any reference repeats the texture, the texture repeats in the + // palette. + if (reference->get_wrap_u() == EggTexture::WM_repeat) { + _position._wrap_u = EggTexture::WM_repeat; + } + if (reference->get_wrap_v() == EggTexture::WM_repeat) { + _position._wrap_v = EggTexture::WM_repeat; + } + } + + // However, if the user specified an explicit wrap mode, allow it to apply. + if (_texture->get_txa_wrap_u() != EggTexture::WM_unspecified) { + _position._wrap_u = _texture->get_txa_wrap_u(); + } + if (_texture->get_txa_wrap_v() != EggTexture::WM_unspecified) { + _position._wrap_v = _texture->get_txa_wrap_v(); + } + + if (!_has_uvs) { + force_replace(); + _omit_reason = OR_unused; + return false; + } + + LTexCoordd rounded_min_uv = min_uv; + LTexCoordd rounded_max_uv = max_uv; + + // cout << get_name() << endl; + + // If so requested, round the minmax out to the next _round_unit. This cuts + // down on unnecessary resizing of textures within the palettes as the egg + // references change in trivial amounts. cout << "rounded_min_uv: " << + // rounded_min_uv << endl; cout << "rounded_max_uv: " << rounded_max_uv << + // endl; + + if (pal->_round_uvs) { + rounded_max_uv[0] = + ceil((rounded_max_uv[0] - pal->_round_fuzz) / pal->_round_unit) * + pal->_round_unit; + rounded_max_uv[1] = + ceil((rounded_max_uv[1] - pal->_round_fuzz) / pal->_round_unit) * + pal->_round_unit; + + rounded_min_uv[0] = + floor((rounded_min_uv[0] + pal->_round_fuzz) / pal->_round_unit) * + pal->_round_unit; + rounded_min_uv[1] = + floor((rounded_min_uv[1] + pal->_round_fuzz) / pal->_round_unit) * + pal->_round_unit; + + // cout << "after rounded_min_uv: " << rounded_min_uv << endl; cout << + // "after rounded_max_uv: " << rounded_max_uv << endl; + } + + // Now determine the size in pixels we require based on the UV's that + // actually reference this texture. + compute_size_from_uvs(rounded_min_uv, rounded_max_uv); + + // Now, can it be placed? + if (_texture->get_omit()) { + // Not if the user says it can't. + force_replace(); + _omit_reason = OR_omitted; + + } else if (get_uv_area() > _texture->get_coverage_threshold()) { + // If the texture repeats too many times, we can't place it. + force_replace(); + _omit_reason = OR_coverage; + + } else if ((_position._x_size > pal->_pal_x_size || + _position._y_size > pal->_pal_y_size) || + (_position._x_size == pal->_pal_x_size && + _position._y_size == pal->_pal_y_size)) { + // If the texture exceeds the size of an empty palette image in either + // dimension, or if it exactly equals the size of an empty palette image + // in both dimensions, we can't place it because it's too big. + force_replace(); + _omit_reason = OR_size; + + } else if (pal->_omit_everything && (_group->is_none_texture_swap())) { + // If we're omitting everything, omit everything. + force_replace(); + _omit_reason = OR_default_omit; + + } else if (_omit_reason == OR_omitted || + _omit_reason == OR_default_omit || + _omit_reason == OR_size || + _omit_reason == OR_coverage || + _omit_reason == OR_unknown) { + // On the other hand, if the texture was previously omitted explicitly, or + // because of its size or coverage, now it seems to fit. + force_replace(); + mark_eggs_stale(); + _omit_reason = OR_working; + + } else if (is_placed()) { + // It *can* be placed. If it was already placed previously, can we leave + // it where it is? + + if (_position._x_size != _placed._x_size || + _position._y_size != _placed._y_size || + _position._min_uv[0] < _placed._min_uv[0] || + _position._min_uv[1] < _placed._min_uv[1] || + _position._max_uv[0] > _placed._max_uv[0] || + _position._max_uv[1] > _placed._max_uv[1]) { + // If the texture was previously placed but is now the wrong size, or if + // the area we need to cover is different, we need to re-place it. + + // However, we make a special exception: if it would have fit without + // rounding up the UV's, then screw rounding it up and just leave it + // alone. + if ((_position._x_size > _placed._x_size || + _position._y_size > _placed._y_size) && + pal->_round_uvs) { + compute_size_from_uvs(min_uv, max_uv); + if (_position._x_size <= _placed._x_size && + _position._y_size <= _placed._y_size && + _position._min_uv[0] >= _placed._min_uv[0] && + _position._min_uv[1] >= _placed._min_uv[1] && + _position._max_uv[0] <= _placed._max_uv[0] && + _position._max_uv[1] <= _placed._max_uv[1]) { + // No problem! It fits here, so leave well enough alone. + } else { + // That's not good enough either, so go back to rounding. + compute_size_from_uvs(rounded_min_uv, rounded_max_uv); + force_replace(); + } + } else { + force_replace(); + } + } + + if (_position._wrap_u != _placed._wrap_u || + _position._wrap_v != _placed._wrap_v) { + // The wrap mode properties have changed slightly. We may or may not + // need to re-place it, but we will need to update it. + _is_filled = false; + _placed._wrap_u = _position._wrap_u; + _placed._wrap_v = _position._wrap_v; + } + } + + return true; +} + +/** + * Returns true if the texture's size is known, false otherwise. Usually this + * can only be false after determine_size() has been called there is something + * wrong with the texture (in which case the placement will automatically omit + * itself from the palette anyway). + */ +bool TexturePlacement:: +is_size_known() const { + return _size_known; +} + +/** + * Returns the reason the texture has been omitted from a palette image, or + * OR_none if it has not. + */ +OmitReason TexturePlacement:: +get_omit_reason() const { + return _omit_reason; +} + +/** + * Returns the size in the X dimension, in pixels, of the texture image as it + * must appear in the palette. This accounts for any growing or shrinking of + * the texture due to the UV coordinate range. + */ +int TexturePlacement:: +get_x_size() const { + nassertr(_size_known, 0); + return _position._x_size; +} + +/** + * Returns the size in the Y dimension, in pixels, of the texture image as it + * must appear in the palette. This accounts for any growing or shrinking of + * the texture due to the UV coordinate range. + */ +int TexturePlacement:: +get_y_size() const { + nassertr(_size_known, 0); + return _position._y_size; +} + +/** + * Returns the total area of the rectangle occupied by the UV minmax box, in + * UV coordinates. 1.0 is the entire texture; values greater than 1 imply the + * texture repeats. + */ +double TexturePlacement:: +get_uv_area() const { + if (!_has_uvs) { + return 0.0; + } + + LTexCoordd range = _position._max_uv - _position._min_uv; + return range[0] * range[1]; +} + +/** + * Returns true if the texture has been placed on a palette image, false + * otherwise. This will generally be true if get_omit_reason() returns + * OR_none or OR_solitary and false otherwise. + */ +bool TexturePlacement:: +is_placed() const { + return _image != nullptr; +} + +/** + * Returns the particular PaletteImage on which the texture has been placed. + */ +PaletteImage *TexturePlacement:: +get_image() const { + nassertr(is_placed(), nullptr); + return _image; +} + +/** + * Returns the particular PalettePage on which the texture has been placed. + */ +PalettePage *TexturePlacement:: +get_page() const { + nassertr(is_placed(), nullptr); + return _image->get_page(); +} + +/** + * Returns the X pixel at which the texture has been placed within its + * PaletteImage. It is an error to call this unless is_placed() returns true. + */ +int TexturePlacement:: +get_placed_x() const { + nassertr(is_placed(), 0); + return _placed._x; +} + +/** + * Returns the Y pixel at which the texture has been placed within its + * PaletteImage. It is an error to call this unless is_placed() returns true. + */ +int TexturePlacement:: +get_placed_y() const { + nassertr(is_placed(), 0); + return _placed._y; +} + +/** + * Returns the size in the X dimension, in pixels, of the texture image as it + * has been placed within the palette. + */ +int TexturePlacement:: +get_placed_x_size() const { + nassertr(is_placed(), 0); + return _placed._x_size; +} + +/** + * Returns the size in the Y dimension, in pixels, of the texture image as it + * has been placed within the palette. + */ +int TexturePlacement:: +get_placed_y_size() const { + nassertr(is_placed(), 0); + return _placed._y_size; +} + +/** + * Returns the total area of the rectangle occupied by the UV minmax box, as + * it has been placed. See also get_uv_area(). + */ +double TexturePlacement:: +get_placed_uv_area() const { + nassertr(is_placed(), 0); + LTexCoordd range = _placed._max_uv - _placed._min_uv; + return range[0] * range[1]; +} + +/** + * Assigns the texture to a particular position within the indicated + * PaletteImage. It is an error to call this if the texture has already been + * placed elsewhere. + */ +void TexturePlacement:: +place_at(PaletteImage *image, int x, int y) { + nassertv(!is_placed()); + nassertv(_size_known); + + _image = image; + _is_filled = false; + _position._x = x; + _position._y = y; + _placed = _position; + _omit_reason = OR_none; +} + +/** + * Removes the texture from its particular PaletteImage, but does not remove + * it from the PaletteGroup. It will be re-placed when the + * PaletteGroup::place_all() is called. + */ +void TexturePlacement:: +force_replace() { + if (_image != nullptr) { + _image->unplace(this); + _image = nullptr; + } + if (_omit_reason == OR_none) { + mark_eggs_stale(); + } + _omit_reason = OR_working; +} + +/** + * Sets the omit reason (returned by get_omit()) to OR_solitary, indicating + * that the palettized version of the texture should not be used because it is + * the only texture on a PaletteImage. However, the texture is still + * considered placed, and is_placed() will return true. + */ +void TexturePlacement:: +omit_solitary() { + nassertv(is_placed()); + if (_omit_reason != OR_solitary) { + mark_eggs_stale(); + _omit_reason = OR_solitary; + } +} + +/** + * Indicates that the texture, formerly indicated as solitary, is now no + * longer. + */ +void TexturePlacement:: +not_solitary() { + nassertv(is_placed()); + if (_omit_reason != OR_none) { + mark_eggs_stale(); + _omit_reason = OR_none; + } +} + +/** + * Returns true if the particular position this texture has been assigned to + * overlaps the rectangle whose top left corner is at x, y and whose size is + * given by x_size, y_size, or false otherwise. + */ +bool TexturePlacement:: +intersects(int x, int y, int x_size, int y_size) { + nassertr(is_placed(), false); + + int hright = x + x_size; + int hbot = y + y_size; + + int mright = _placed._x + _placed._x_size; + int mbot = _placed._y + _placed._y_size; + + return !(x >= mright || hright <= _placed._x || + y >= mbot || hbot <= _placed._y); +} + +/** + * Stores in the indicated matrix the appropriate texture matrix transform for + * the new placement of the texture. + */ +void TexturePlacement:: +compute_tex_matrix(LMatrix3d &transform) { + nassertv(is_placed()); + + LMatrix3d source_uvs = LMatrix3d::ident_mat(); + + LTexCoordd range = _placed._max_uv - _placed._min_uv; + if (range[0] != 0.0 && range[1] != 0.0) { + source_uvs = + LMatrix3d::translate_mat(-_placed._min_uv) * + LMatrix3d::scale_mat(1.0 / range[0], 1.0 / range[1]); + } + + int top = _placed._y + _placed._margin; + int left = _placed._x + _placed._margin; + int x_size = _placed._x_size - _placed._margin * 2; + int y_size = _placed._y_size - _placed._margin * 2; + + int bottom = top + y_size; + int pal_x_size = _image->get_x_size(); + int pal_y_size = _image->get_y_size(); + + LVecBase2d t((double)left / (double)pal_x_size, + (double)(pal_y_size - bottom) / (double)pal_y_size); + LVecBase2d s((double)x_size / (double)pal_x_size, + (double)y_size / (double)pal_y_size); + + LMatrix3d dest_uvs + (s[0], 0.0, 0.0, + 0.0, s[1], 0.0, + t[0], t[1], 1.0); + + transform = source_uvs * dest_uvs; +} + +/** + * Writes the placement position information on a line by itself. + */ +void TexturePlacement:: +write_placed(std::ostream &out, int indent_level) { + indent(out, indent_level) + << get_texture()->get_name(); + + if (is_placed()) { + out << " at " + << get_placed_x() << " " << get_placed_y() << " to " + << get_placed_x() + get_placed_x_size() << " " + << get_placed_y() + get_placed_y_size() << " (coverage " + << get_placed_uv_area() << ")"; + + if (_placed._wrap_u != EggTexture::WM_unspecified || + _placed._wrap_v != EggTexture::WM_unspecified) { + if (_placed._wrap_u != _placed._wrap_v) { + out << " (" << _placed._wrap_u << ", " << _placed._wrap_v << ")"; + } else { + out << " " << _placed._wrap_u; + } + } + out << "\n"; + } else { + out << " not yet placed.\n"; + } +}; + +/** + * Returns true if the texture has been filled (i.e. fill_image() has been + * called) since it was placed. + */ +bool TexturePlacement:: +is_filled() const { + return _is_filled; +} + +/** + * Marks the texture as unfilled, so that it will need to be copied into the + * palette image again. + */ +void TexturePlacement:: +mark_unfilled() { + _is_filled = false; +} + +/** + * Fills in the rectangle of the palette image represented by the texture + * placement with the image pixels. + */ +void TexturePlacement:: +fill_image(PNMImage &image) { + nassertv(is_placed()); + + _is_filled = true; + + // We determine the pixels to place the source image at by transforming the + // unit texture box: the upper-left and lower-right corners. These corners, + // in the final texture coordinate space, represent where on the palette + // image the original texture should be located. + + LMatrix3d transform; + compute_tex_matrix(transform); + LTexCoordd ul = LTexCoordd(0.0, 1.0) * transform; + LTexCoordd lr = LTexCoordd(1.0, 0.0) * transform; + + // Now we convert those texture coordinates back to pixel units. + int pal_x_size = _image->get_x_size(); + int pal_y_size = _image->get_y_size(); + + int top = (int)floor((1.0 - ul[1]) * pal_y_size + 0.5); + int left = (int)floor(ul[0] * pal_x_size + 0.5); + int bottom = (int)floor((1.0 - lr[1]) * pal_y_size + 0.5); + int right = (int)floor(lr[0] * pal_x_size + 0.5); + + // And now we can determine the size to scale the image to based on that. + // This may not be the same as texture->size() because of margins. + int x_size = right - left; + int y_size = bottom - top; + nassertv(x_size >= 0 && y_size >= 0); + + // Now we get a PNMImage that represents the source texture at that size. + const PNMImage &source_full = _texture->read_source_image(); + if (!source_full.is_valid()) { + flag_error_image(image); + return; + } + + PNMImage source(x_size, y_size, source_full.get_num_channels(), + source_full.get_maxval()); + source.quick_filter_from(source_full); + + bool alpha = image.has_alpha(); + bool source_alpha = source.has_alpha(); + + // Now copy the pixels. We do this by walking through the rectangular + // region on the palette image that we have reserved for this texture; for + // each pixel in this region, we determine its appropriate color based on + // its relation to the actual texture image location (determined above), and + // on whether the texture wraps or clamps. + for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) { + int sy = y - top; + + switch (_placed._wrap_v) { + case EggTexture::WM_clamp: + // Clamp at [0, y_size). + sy = max(min(sy, y_size - 1), 0); + break; + + case EggTexture::WM_mirror: + sy = (sy < 0) ? (y_size * 2) - 1 - ((-sy - 1) % (y_size * 2)) : sy % (y_size * 2); + sy = (sy < y_size) ? sy : 2 * y_size - sy - 1; + break; + + case EggTexture::WM_mirror_once: + sy = (sy < y_size) ? sy : 2 * y_size - sy - 1; + // Fall through + + case EggTexture::WM_border_color: + if (sy < 0 || sy >= y_size) { + continue; + } + break; + + default: + // Wrap: sign-independent modulo. + sy = (sy < 0) ? y_size - 1 - ((-sy - 1) % y_size) : sy % y_size; + break; + } + + for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) { + int sx = x - left; + + switch (_placed._wrap_u) { + case EggTexture::WM_clamp: + // Clamp at [0, x_size). + sx = max(min(sx, x_size - 1), 0); + break; + + case EggTexture::WM_mirror: + sx = (sx < 0) ? (x_size * 2) - 1 - ((-sx - 1) % (x_size * 2)) : sx % (x_size * 2); + sx = (sx < x_size) ? sx : 2 * x_size - sx - 1; + break; + + case EggTexture::WM_mirror_once: + sx = (sx >= 0) ? sx : ~sx; + // Fall through + + case EggTexture::WM_border_color: + if (sx < 0 || sx >= x_size) { + continue; + } + break; + + default: + // Wrap: sign-independent modulo. + sx = (sx < 0) ? x_size - 1 - ((-sx - 1) % x_size) : sx % x_size; + break; + } + + image.set_xel(x, y, source.get_xel(sx, sy)); + if (alpha) { + if (source_alpha) { + image.set_alpha(x, y, source.get_alpha(sx, sy)); + } else { + image.set_alpha(x, y, 1.0); + } + } + } + } + + _texture->release_source_image(); +} + + +/** + * Fills in the rectangle of the swapped palette image represented by the + * texture placement with the image pixels. + */ +void TexturePlacement:: +fill_swapped_image(PNMImage &image, int index) { + nassertv(is_placed()); + + _is_filled = true; + + // We determine the pixels to place the source image at by transforming the + // unit texture box: the upper-left and lower-right corners. These corners, + // in the final texture coordinate space, represent where on the palette + // image the original texture should be located. + + LMatrix3d transform; + compute_tex_matrix(transform); + LTexCoordd ul = LTexCoordd(0.0, 1.0) * transform; + LTexCoordd lr = LTexCoordd(1.0, 0.0) * transform; + + // Now we convert those texture coordinates back to pixel units. + int pal_x_size = _image->get_x_size(); + int pal_y_size = _image->get_y_size(); + + int top = (int)floor((1.0 - ul[1]) * pal_y_size + 0.5); + int left = (int)floor(ul[0] * pal_x_size + 0.5); + int bottom = (int)floor((1.0 - lr[1]) * pal_y_size + 0.5); + int right = (int)floor(lr[0] * pal_x_size + 0.5); + + // And now we can determine the size to scale the image to based on that. + // This may not be the same as texture->size() because of margins. + int x_size = right - left; + int y_size = bottom - top; + nassertv(x_size >= 0 && y_size >= 0); + + // Now we get a PNMImage that represents the swapped texture at that size. + TextureSwaps::iterator tsi; + tsi = _textureSwaps.begin() + index; + TextureImage *swapTexture = (*tsi); + const PNMImage &source_full = swapTexture->read_source_image(); + if (!source_full.is_valid()) { + flag_error_image(image); + return; + } + + PNMImage source(x_size, y_size, source_full.get_num_channels(), + source_full.get_maxval()); + source.quick_filter_from(source_full); + + bool alpha = image.has_alpha(); + bool source_alpha = source.has_alpha(); + + // Now copy the pixels. We do this by walking through the rectangular + // region on the palette image that we have reserved for this texture; for + // each pixel in this region, we determine its appropriate color based on + // its relation to the actual texture image location (determined above), and + // on whether the texture wraps or clamps. + for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) { + int sy = y - top; + + if (_placed._wrap_v == EggTexture::WM_clamp) { + // Clamp at [0, y_size). + sy = max(min(sy, y_size - 1), 0); + + } else { + // Wrap: sign-independent modulo. + sy = (sy < 0) ? y_size - 1 - ((-sy - 1) % y_size) : sy % y_size; + } + + for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) { + int sx = x - left; + + if (_placed._wrap_u == EggTexture::WM_clamp) { + // Clamp at [0, x_size). + sx = max(min(sx, x_size - 1), 0); + + } else { + // Wrap: sign-independent modulo. + sx = (sx < 0) ? x_size - 1 - ((-sx - 1) % x_size) : sx % x_size; + } + + image.set_xel(x, y, source.get_xel(sx, sy)); + if (alpha) { + if (source_alpha) { + image.set_alpha(x, y, source.get_alpha(sx, sy)); + } else { + image.set_alpha(x, y, 1.0); + } + } + } + } + + swapTexture->release_source_image(); +} + +/** + * Sets the rectangle of the palette image represented by the texture + * placement to red, to represent a missing texture. + */ +void TexturePlacement:: +flag_error_image(PNMImage &image) { + nassertv(is_placed()); + for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) { + for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) { + image.set_xel_val(x, y, 1, 0, 0); + } + } + if (image.has_alpha()) { + for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) { + for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) { + image.set_alpha_val(x, y, 1); + } + } + } +} + +/** + * A support function for determine_size(), this computes the appropriate size + * of the texture in pixels based on the UV coverage (as well as on the size + * of the source texture). + */ +void TexturePlacement:: +compute_size_from_uvs(const LTexCoordd &min_uv, const LTexCoordd &max_uv) { + _position._min_uv = min_uv; + _position._max_uv = max_uv; + + LTexCoordd range = _position._max_uv - _position._min_uv; + // cout << "range: " << range << endl; + + // cout << "_x_size texture: " << _texture->get_x_size() << endl; cout << + // "_y_size texture: " << _texture->get_y_size() << endl; + + _position._x_size = (int)floor(_texture->get_x_size() * range[0] + 0.5); + _position._y_size = (int)floor(_texture->get_y_size() * range[1] + 0.5); + + // cout << "_x_size: " << _position._x_size << endl; cout << "_y_size: " << + // _position._y_size << endl; + + // We arbitrarily require at least four pixels in each dimension. Fewer + // than this may be asking for trouble. + _position._x_size = max(_position._x_size, 4); + _position._y_size = max(_position._y_size, 4); + + if(get_group()->has_margin_override()) { + _position._margin = get_group()->get_margin_override(); + } else { + _position._margin = _texture->get_margin(); + } + // cout << "margin: " << _position._margin << endl; + + // Normally, we have interior margins, but if the image size is too small-- + // i.e. the margin size is too great a percentage of the image size--we'll + // make them exterior margins so as not to overly degrade the quality of the + // image. + if ((double)_position._margin / (double)_position._x_size > 0.10) { + _position._x_size += _position._margin * 2; + } + if ((double)_position._margin / (double)_position._y_size > 0.10) { + _position._y_size += _position._margin * 2; + } + + _size_known = true; +} + + + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void TexturePlacement:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void TexturePlacement:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + writer->write_pointer(datagram, _texture); + writer->write_pointer(datagram, _group); + writer->write_pointer(datagram, _image); + writer->write_pointer(datagram, _dest); + + datagram.add_bool(_has_uvs); + datagram.add_bool(_size_known); + _position.write_datagram(writer, datagram); + + datagram.add_bool(_is_filled); + _placed.write_datagram(writer, datagram); + datagram.add_int32((int)_omit_reason); + + datagram.add_int32(_references.size()); + References::const_iterator ri; + for (ri = _references.begin(); ri != _references.end(); ++ri) { + writer->write_pointer(datagram, (*ri)); + } + + datagram.add_int32(_textureSwaps.size()); + TextureSwaps::const_iterator tsi; + for (tsi = _textureSwaps.begin(); tsi != _textureSwaps.end(); ++tsi) { + writer->write_pointer(datagram, (*tsi)); + } + +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int TexturePlacement:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int index = TypedWritable::complete_pointers(p_list, manager); + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_texture, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_group, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_image, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_dest, p_list[index], index); + } + index++; + + int i; + for (i = 0; i < _num_references; i++) { + TextureReference *reference; + DCAST_INTO_R(reference, p_list[index], index); + _references.insert(reference); + index++; + } + + for (i = 0; i < _num_textureSwaps; i++) { + TextureImage *swapTexture; + DCAST_INTO_R(swapTexture, p_list[index], index); + _textureSwaps.push_back(swapTexture); + index++; + } + + return index; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *TexturePlacement:: +make_from_bam(const FactoryParams ¶ms) { + TexturePlacement *me = new TexturePlacement; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void TexturePlacement:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + + manager->read_pointer(scan); // _texture + manager->read_pointer(scan); // _group + manager->read_pointer(scan); // _image + manager->read_pointer(scan); // _dest + + _has_uvs = scan.get_bool(); + _size_known = scan.get_bool(); + _position.fillin(scan, manager); + + _is_filled = scan.get_bool(); + _placed.fillin(scan, manager); + _omit_reason = (OmitReason)scan.get_int32(); + + _num_references = scan.get_int32(); + manager->read_pointers(scan, _num_references); + + if (Palettizer::_read_pi_version >= 20) { + _num_textureSwaps = scan.get_int32(); + } else { + _num_textureSwaps = 0; + } + manager->read_pointers(scan, _num_textureSwaps); +} + + +/** + * Compares two TexturePlacement objects and returns true if the first one is + * bigger than the second one, false otherwise. + */ +bool SortPlacementBySize:: +operator ()(TexturePlacement *a, TexturePlacement *b) const { + if (a->get_y_size() < b->get_y_size()) { + return false; + + } else if (b->get_y_size() < a->get_y_size()) { + return true; + + } else if (a->get_x_size() < b->get_x_size()) { + return false; + + } else if (b->get_x_size() < a->get_x_size()) { + return true; + } else if (a->get_name() < b->get_name()) { + // use this fall through case to let alphabetically smaller textures show + // up first + return true; + } + + return false; +} diff --git a/pandatool/src/palettizer/texturePlacement.h b/pandatool/src/palettizer/texturePlacement.h new file mode 100644 index 00000000..efe90f85 --- /dev/null +++ b/pandatool/src/palettizer/texturePlacement.h @@ -0,0 +1,157 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file texturePlacement.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef TEXTUREPLACEMENT_H +#define TEXTUREPLACEMENT_H + +#include "pandatoolbase.h" + +#include "omitReason.h" +#include "texturePosition.h" + +#include "typedWritable.h" +#include "luse.h" + +#include "pset.h" + +class TextureImage; +class DestTextureImage; +class PaletteGroup; +class PaletteImage; +class PalettePage; +class TextureProperties; +class TextureReference; +class PNMImage; + +/** + * This corresponds to a particular assignment of a TextureImage with a + * PaletteGroup, and specifically describes which PaletteImage (if any), and + * where on the PaletteImage, the TextureImage has been assigned to. + */ +class TexturePlacement : public TypedWritable { +private: + TexturePlacement(); + +public: + TexturePlacement(TextureImage *texture, PaletteGroup *group); + ~TexturePlacement(); + + const std::string &get_name() const; + TextureImage *get_texture() const; + const TextureProperties &get_properties() const; + PaletteGroup *get_group() const; + + void add_egg(TextureReference *reference); + void remove_egg(TextureReference *reference); + void mark_eggs_stale(); + + void set_dest(DestTextureImage *dest); + DestTextureImage *get_dest() const; + + bool determine_size(); + bool is_size_known() const; + OmitReason get_omit_reason() const; + int get_x_size() const; + int get_y_size() const; + double get_uv_area() const; + + bool is_placed() const; + PaletteImage *get_image() const; + PalettePage *get_page() const; + int get_placed_x() const; + int get_placed_y() const; + int get_placed_x_size() const; + int get_placed_y_size() const; + double get_placed_uv_area() const; + + void place_at(PaletteImage *image, int x, int y); + void force_replace(); + void omit_solitary(); + void not_solitary(); + bool intersects(int x, int y, int x_size, int y_size); + + void compute_tex_matrix(LMatrix3d &transform); + + void write_placed(std::ostream &out, int indent_level = 0); + + bool is_filled() const; + void mark_unfilled(); + void fill_image(PNMImage &image); + void fill_swapped_image(PNMImage &image, int index); + void flag_error_image(PNMImage &image); + + typedef pvector TextureSwaps; + TextureSwaps _textureSwaps; + +private: + void compute_size_from_uvs(const LTexCoordd &min_uv, const LTexCoordd &max_uv); + + TextureImage *_texture; + PaletteGroup *_group; + PaletteImage *_image; + DestTextureImage *_dest; + + bool _has_uvs; + bool _size_known; + TexturePosition _position; + + bool _is_filled; + TexturePosition _placed; + OmitReason _omit_reason; + + typedef pset References; + References _references; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +private: + // This value is only filled in while reading from the bam file; don't use + // it otherwise. + int _num_references; + int _num_textureSwaps; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "TexturePlacement", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + + +// This is an STL object to sort an array of TexturePlacement pointers in +// order from biggest to smallest. +class SortPlacementBySize { +public: + bool operator ()(TexturePlacement *a, TexturePlacement *b) const; +}; + +#endif diff --git a/pandatool/src/palettizer/texturePosition.cxx b/pandatool/src/palettizer/texturePosition.cxx new file mode 100644 index 00000000..ea4f61ac --- /dev/null +++ b/pandatool/src/palettizer/texturePosition.cxx @@ -0,0 +1,135 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file texturePosition.cxx + * @author drose + * @date 2000-12-04 + */ + +#include "texturePosition.h" + +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" + +TypeHandle TexturePosition::_type_handle; + +/** + * + */ +TexturePosition:: +TexturePosition() { + _margin = 0; + _x = 0; + _y = 0; + _x_size = 0; + _y_size = 0; + _min_uv.set(0.0, 0.0); + _max_uv.set(0.0, 0.0); + _wrap_u = EggTexture::WM_unspecified; + _wrap_v = EggTexture::WM_unspecified; +} + +/** + * + */ +TexturePosition:: +TexturePosition(const TexturePosition ©) : + _margin(copy._margin), + _x(copy._x), + _y(copy._y), + _x_size(copy._x_size), + _y_size(copy._y_size), + _min_uv(copy._min_uv), + _max_uv(copy._max_uv), + _wrap_u(copy._wrap_u), + _wrap_v(copy._wrap_v) +{ +} + +/** + * + */ +void TexturePosition:: +operator = (const TexturePosition ©) { + _margin = copy._margin; + _x = copy._x; + _y = copy._y; + _x_size = copy._x_size; + _y_size = copy._y_size; + _min_uv = copy._min_uv; + _max_uv = copy._max_uv; + _wrap_u = copy._wrap_u; + _wrap_v = copy._wrap_v; +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void TexturePosition:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void TexturePosition:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_int32(_margin); + datagram.add_int32(_x); + datagram.add_int32(_y); + datagram.add_int32(_x_size); + datagram.add_int32(_y_size); + datagram.add_float64(_min_uv[0]); + datagram.add_float64(_min_uv[1]); + datagram.add_float64(_max_uv[0]); + datagram.add_float64(_max_uv[1]); + datagram.add_int32((int)_wrap_u); + datagram.add_int32((int)_wrap_v); +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *TexturePosition:: +make_from_bam(const FactoryParams ¶ms) { + TexturePosition *me = new TexturePosition; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void TexturePosition:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + _margin = scan.get_int32(); + _x = scan.get_int32(); + _y = scan.get_int32(); + _x_size = scan.get_int32(); + _y_size = scan.get_int32(); + _min_uv[0] = scan.get_float64(); + _min_uv[1] = scan.get_float64(); + _max_uv[0] = scan.get_float64(); + _max_uv[1] = scan.get_float64(); + _wrap_u = (EggTexture::WrapMode)scan.get_int32(); + _wrap_v = (EggTexture::WrapMode)scan.get_int32(); +} diff --git a/pandatool/src/palettizer/texturePosition.h b/pandatool/src/palettizer/texturePosition.h new file mode 100644 index 00000000..c1edd40e --- /dev/null +++ b/pandatool/src/palettizer/texturePosition.h @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file texturePosition.h + * @author drose + * @date 2000-12-04 + */ + +#ifndef TEXTUREPOSITION_H +#define TEXTUREPOSITION_H + +#include "pandatoolbase.h" + +#include "typedWritable.h" +#include "luse.h" +#include "eggTexture.h" + +class FactoryParams; + +/** + * This represents a particular position of a texture within a PaletteImage. + * There is only one of these per TexturePlacement, but it exists as a + * separate structure so the TexturePlacement can easily consider + * repositioning the texture. + */ +class TexturePosition : public TypedWritable { +public: + TexturePosition(); + TexturePosition(const TexturePosition ©); + void operator = (const TexturePosition ©); + + int _margin; + int _x, _y; + int _x_size, _y_size; + + LTexCoordd _min_uv; + LTexCoordd _max_uv; + + EggTexture::WrapMode _wrap_u; + EggTexture::WrapMode _wrap_v; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + +public: + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "TexturePosition", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/palettizer/textureProperties.cxx b/pandatool/src/palettizer/textureProperties.cxx new file mode 100644 index 00000000..c5635409 --- /dev/null +++ b/pandatool/src/palettizer/textureProperties.cxx @@ -0,0 +1,938 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureProperties.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "textureProperties.h" +#include "palettizer.h" +#include "pnmFileType.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "string_utils.h" + +using std::string; + +TypeHandle TextureProperties::_type_handle; + +/** + * + */ +TextureProperties:: +TextureProperties() { + _got_num_channels = false; + _num_channels = 0; + _effective_num_channels = 0; + _format = EggTexture::F_unspecified; + _force_format = false; + _generic_format = false; + _keep_format = false; + _minfilter = EggTexture::FT_unspecified; + _magfilter = EggTexture::FT_unspecified; + _quality_level = EggTexture::QL_unspecified; + _anisotropic_degree = 0; + _color_type = nullptr; + _alpha_type = nullptr; + _srgb = false; +} + +/** + * + */ +TextureProperties:: +TextureProperties(const TextureProperties ©) : + _format(copy._format), + _force_format(copy._force_format), + _generic_format(copy._generic_format), + _keep_format(copy._keep_format), + _minfilter(copy._minfilter), + _magfilter(copy._magfilter), + _quality_level(copy._quality_level), + _anisotropic_degree(copy._anisotropic_degree), + _color_type(copy._color_type), + _alpha_type(copy._alpha_type), + _srgb(copy._srgb), + _got_num_channels(copy._got_num_channels), + _num_channels(copy._num_channels), + _effective_num_channels(copy._effective_num_channels) +{ +} + +/** + * + */ +void TextureProperties:: +operator = (const TextureProperties ©) { + _force_format = copy._force_format; + _generic_format = copy._generic_format; + _keep_format = copy._keep_format; + _minfilter = copy._minfilter; + _magfilter = copy._magfilter; + _quality_level = copy._quality_level; + _anisotropic_degree = copy._anisotropic_degree; + _color_type = copy._color_type; + _alpha_type = copy._alpha_type; + _got_num_channels = copy._got_num_channels; + _num_channels = copy._num_channels; + _effective_num_channels = copy._effective_num_channels; + _format = copy._format; + _srgb = copy._srgb; +} + +/** + * Resets only the properties that might be changed by update_properties() to + * a neutral state. + */ +void TextureProperties:: +clear_basic() { + if (!_force_format) { + _format = EggTexture::F_unspecified; + } + + _minfilter = EggTexture::FT_unspecified; + _magfilter = EggTexture::FT_unspecified; + _quality_level = EggTexture::QL_unspecified; + _anisotropic_degree = 0; +} + +/** + * Returns true if the number of channels is known. + */ +bool TextureProperties:: +has_num_channels() const { + return _got_num_channels; +} + +/** + * Returns the number of channels (1 through 4) associated with the image. It + * is an error to call this unless has_num_channels() returns true. + */ +int TextureProperties:: +get_num_channels() const { + nassertr(_got_num_channels, 0); + return _effective_num_channels; +} + +/** + * Sets the number of channels (1 through 4) associated with the image, + * presumably after reading this information from the image header. + */ +void TextureProperties:: +set_num_channels(int num_channels) { + _num_channels = num_channels; + _effective_num_channels = num_channels; + _got_num_channels = true; +} + +/** + * Sets the actual number of channels to indicate a grayscale image, + * presumably after discovering that the image contains no colored pixels. + */ +void TextureProperties:: +force_grayscale() { + nassertv(_got_num_channels && _num_channels >= 3); + _num_channels -= 2; + _effective_num_channels = _num_channels; +} + +/** + * Sets the actual number of channels to indicate an image with no alpha + * channel, presumably after discovering that the alpha channel contains no + * meaningful pixels. + */ +void TextureProperties:: +force_nonalpha() { + nassertv(_got_num_channels && (_num_channels == 2 || _num_channels == 4)); + _num_channels--; + _effective_num_channels = _num_channels; +} + +/** + * Returns true if the texture uses an alpha channel, false otherwise. + */ +bool TextureProperties:: +uses_alpha() const { + switch (_format) { + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgba12: + case EggTexture::F_rgba8: + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + case EggTexture::F_alpha: + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + case EggTexture::F_srgb_alpha: + case EggTexture::F_sluminance_alpha: + return true; + + default: + return false; + } +} + +/** + * Returns a string corresponding to the TextureProperties object. Each + * unique set of TextureProperties will generate a unique string. This is + * used to generate unique palette image filenames. + */ +string TextureProperties:: +get_string() const { + string result; + + if (_got_num_channels) { + std::ostringstream num; + num << _effective_num_channels; + result += num.str(); + } + + result += get_format_string(_format); + result += get_filter_string(_minfilter); + result += get_filter_string(_magfilter); + result += get_anisotropic_degree_string(_anisotropic_degree); + result += get_type_string(_color_type, _alpha_type); + result += get_quality_level_string(_quality_level); + return result; +} + +/** + * If the indicate TextureProperties structure is more specific than this one, + * updates this one. + */ +void TextureProperties:: +update_properties(const TextureProperties &other) { + if (!_got_num_channels) { + _got_num_channels = other._got_num_channels; + _num_channels = other._num_channels; + _effective_num_channels = _num_channels; + } + + _srgb = other._srgb; + + if (_force_format) { + // If we've forced our own format, it doesn't change. + } else if (other._force_format) { + _format = other._format; + } else { + _format = union_format(_format, other._format); + } + + _minfilter = union_filter(_minfilter, other._minfilter); + _magfilter = union_filter(_magfilter, other._magfilter); + _quality_level = union_quality_level(_quality_level, other._quality_level); + + _anisotropic_degree = other._anisotropic_degree; + + if (_color_type == nullptr) { + _color_type = other._color_type; + _alpha_type = other._alpha_type; + } +} + +/** + * If any properties remain unspecified, specify them now. Also reconcile + * conflicting information. + */ +void TextureProperties:: +fully_define() { + if (!_got_num_channels || _force_format) { + switch (_format) { + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgba12: + case EggTexture::F_rgba8: + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + case EggTexture::F_srgb_alpha: + _num_channels = 4; + break; + + case EggTexture::F_unspecified: + case EggTexture::F_rgb: + case EggTexture::F_rgb12: + case EggTexture::F_rgb8: + case EggTexture::F_rgb5: + case EggTexture::F_rgb332: + case EggTexture::F_srgb: + _num_channels = 3; + break; + + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + case EggTexture::F_sluminance_alpha: + _num_channels = 2; + break; + + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_sluminance: + _num_channels = 1; + break; + } + _got_num_channels = true; + } + + _effective_num_channels = _num_channels; + + // Respect the _generic_format flag. If this is set, it means the user has + // indicated that we should strip off any bitcount-specific formats and + // replace them with the more generic equivalents. + if (_generic_format) { + switch (_format) { + case EggTexture::F_unspecified: + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgb: + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + case EggTexture::F_srgb: + case EggTexture::F_srgb_alpha: + case EggTexture::F_sluminance: + case EggTexture::F_sluminance_alpha: + break; + + case EggTexture::F_rgba12: + case EggTexture::F_rgba8: + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + _format = EggTexture::F_rgba; + break; + + case EggTexture::F_rgb12: + case EggTexture::F_rgb8: + case EggTexture::F_rgb5: + case EggTexture::F_rgb332: + _format = EggTexture::F_rgb; + break; + } + } + + // Make sure the format reflects the number of channels, although we accept + // a format that ignores an alpha channel. + if (!_force_format && !_keep_format) { + switch (_num_channels) { + case 1: + switch (_format) { + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_sluminance: + break; + + // These formats suggest an alpha channel; they are quietly replaced + // with non-alpha equivalents. + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + _format = EggTexture::F_luminance; + break; + + case EggTexture::F_sluminance_alpha: + _format = EggTexture::F_sluminance; + break; + + default: + if (_srgb) { + _format = EggTexture::F_sluminance; + } else { + _format = EggTexture::F_luminance; + } + } + break; + + case 2: + switch (_format) { + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + case EggTexture::F_sluminance_alpha: + break; + + // These formats implicitly reduce the number of channels to 1. + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_sluminance: + break; + + default: + if (_srgb) { + _format = EggTexture::F_sluminance_alpha; + } else { + _format = EggTexture::F_luminance_alpha; + } + } + break; + + case 3: + switch (_format) { + case EggTexture::F_rgb: + case EggTexture::F_rgb12: + case EggTexture::F_rgb8: + case EggTexture::F_rgb5: + case EggTexture::F_rgb332: + case EggTexture::F_srgb: + break; + + // These formats suggest an alpha channel; they are quietly replaced + // with non-alpha equivalents. + case EggTexture::F_rgba8: + _format = EggTexture::F_rgb8; + break; + + case EggTexture::F_rgba5: + case EggTexture::F_rgba4: + _format = EggTexture::F_rgb5; + break; + + // These formats implicitly reduce the number of channels to 1. + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_sluminance: + break; + + default: + if (_srgb) { + _format = EggTexture::F_srgb; + } else { + _format = EggTexture::F_rgb; + } + } + break; + + case 4: + switch (_format) { + case EggTexture::F_rgba: + case EggTexture::F_rgbm: + case EggTexture::F_rgba12: + case EggTexture::F_rgba8: + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + case EggTexture::F_srgb_alpha: + break; + + // These formats implicitly reduce the number of channels to 3. + case EggTexture::F_rgb: + case EggTexture::F_rgb12: + case EggTexture::F_rgb8: + case EggTexture::F_rgb5: + case EggTexture::F_rgb332: + case EggTexture::F_srgb: + _effective_num_channels = 3; + break; + + // These formats implicitly reduce the number of channels to 2. + case EggTexture::F_luminance_alpha: + case EggTexture::F_luminance_alphamask: + case EggTexture::F_sluminance_alpha: + _effective_num_channels = 2; + break; + + // These formats implicitly reduce the number of channels to 1. + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + case EggTexture::F_luminance: + case EggTexture::F_sluminance: + _effective_num_channels = 1; + break; + + default: + if (_srgb) { + _format = EggTexture::F_srgb_alpha; + } else { + _format = EggTexture::F_rgba; + } + } + } + } + + // Respect the _srgb flag. If this is set, it means the texture is in sRGB + // color space and the format should be changed to reflect that. + if (_srgb) { + switch (_num_channels) { + case 1: + // Don't respect sRGB for textures using the F_alpha format, which + // indicates that the image represents an alpha channel, not a color + // channel. + if (_format != EggTexture::F_alpha) { + _format = EggTexture::F_sluminance; + } + break; + case 2: + _format = EggTexture::F_sluminance_alpha; + break; + case 3: + _format = EggTexture::F_srgb; + break; + case 4: + default: + _format = EggTexture::F_srgb_alpha; + break; + } + } + + switch (_minfilter) { + case EggTexture::FT_unspecified: + _minfilter = EggTexture::FT_linear; + break; + + default: + break; + } + + switch (_magfilter) { + case EggTexture::FT_unspecified: + case EggTexture::FT_nearest_mipmap_nearest: + case EggTexture::FT_linear_mipmap_nearest: + case EggTexture::FT_nearest_mipmap_linear: + case EggTexture::FT_linear_mipmap_linear: + _magfilter = EggTexture::FT_linear; + break; + + default: + break; + } + + if (_color_type == nullptr) { + _color_type = pal->_color_type; + _alpha_type = pal->_alpha_type; + } +} + +/** + * Adjusts the texture properties of the indicated egg reference to match + * these properties. + */ +void TextureProperties:: +update_egg_tex(EggTexture *egg_tex) const { + egg_tex->set_format(_format); + egg_tex->set_minfilter(_minfilter); + egg_tex->set_magfilter(_minfilter); + egg_tex->set_quality_level(_quality_level); + egg_tex->set_anisotropic_degree(_anisotropic_degree); +} + +/** + * Returns true if all of the properties that are reflected directly in an egg + * file match between this TextureProperties object and the other, or false if + * any of them differ. + */ +bool TextureProperties:: +egg_properties_match(const TextureProperties &other) const { + return (_format == other._format && + _minfilter == other._minfilter && + _magfilter == other._magfilter && + _quality_level == other._quality_level && + _anisotropic_degree == other._anisotropic_degree); +} + +/** + * + */ +bool TextureProperties:: +operator < (const TextureProperties &other) const { + if (_format != other._format) { + return (int)_format < (int)other._format; + } + if (_minfilter != other._minfilter) { + return (int)_minfilter < (int)other._minfilter; + } + if (_magfilter != other._magfilter) { + return (int)_magfilter < (int)other._magfilter; + } + if (_quality_level != other._quality_level) { + return (int)_quality_level < (int)other._quality_level; + } + if (_anisotropic_degree != other._anisotropic_degree) { + return _anisotropic_degree < other._anisotropic_degree; + } + if (_srgb != other._srgb) { + return _srgb < other._srgb; + } + if (_color_type != other._color_type) { + return _color_type < other._color_type; + } + if (_color_type != nullptr) { + if (_alpha_type != other._alpha_type) { + return _alpha_type < other._alpha_type; + } + } + return false; +} + +/** + * + */ +bool TextureProperties:: +operator == (const TextureProperties &other) const { + return (_format == other._format && + _minfilter == other._minfilter && + _magfilter == other._magfilter && + _quality_level == other._quality_level && + _anisotropic_degree == other._anisotropic_degree && + _srgb == other._srgb && + _color_type == other._color_type && + (_color_type == nullptr || + _alpha_type == other._alpha_type)); +} + +/** + * + */ +bool TextureProperties:: +operator != (const TextureProperties &other) const { + return !operator == (other); +} + +/** + * Returns a short string representing the given EggTexture format. + */ +string TextureProperties:: +get_format_string(EggTexture::Format format) { + switch (format) { + case EggTexture::F_unspecified: + return "u"; + + case EggTexture::F_rgba: + return "a"; + + case EggTexture::F_rgbm: + return "m"; + + case EggTexture::F_rgba12: + return "a12"; + + case EggTexture::F_rgba8: + return "a8"; + + case EggTexture::F_rgba4: + return "a4"; + + case EggTexture::F_rgba5: + return "a5"; + + case EggTexture::F_rgb: + return "c"; + + case EggTexture::F_rgb12: + return "c12"; + + case EggTexture::F_rgb8: + return "c8"; + + case EggTexture::F_rgb5: + return "c5"; + + case EggTexture::F_rgb332: + return "c3"; + + case EggTexture::F_luminance_alpha: + return "t"; // t for two-channel + + case EggTexture::F_luminance_alphamask: + return "t1"; + + case EggTexture::F_red: + return "r"; + + case EggTexture::F_green: + return "g"; + + case EggTexture::F_blue: + return "b"; + + case EggTexture::F_alpha: + return "a"; + + case EggTexture::F_luminance: + return "l"; + + case EggTexture::F_srgb: + return "sc"; + + case EggTexture::F_srgb_alpha: + return "sa"; + + case EggTexture::F_sluminance: + return "sl"; + + case EggTexture::F_sluminance_alpha: + return "st"; + } + + return "x"; +} + +/** + * Returns a short string representing the given EggTexture filter type. + */ +string TextureProperties:: +get_filter_string(EggTexture::FilterType filter_type) { + switch (filter_type) { + case EggTexture::FT_unspecified: + return "u"; + + case EggTexture::FT_nearest: + return "n"; + + case EggTexture::FT_linear: + return "l"; + + case EggTexture::FT_nearest_mipmap_nearest: + return "m1"; + + case EggTexture::FT_linear_mipmap_nearest: + return "m2"; + + case EggTexture::FT_nearest_mipmap_linear: + return "m3"; + + case EggTexture::FT_linear_mipmap_linear: + return "m"; + } + + return "x"; +} + +/** + * Returns a short string describing the anisotropic degree. + */ +string TextureProperties:: +get_anisotropic_degree_string(int aniso_degree) { + if (aniso_degree <= 1) { + return ""; + } else { + return string("an") + format_string(aniso_degree); + } +} + +/** + * Returns a short string describing the quality level. + */ +string TextureProperties:: +get_quality_level_string(EggTexture::QualityLevel quality_level) { + switch (quality_level) { + case EggTexture::QL_unspecified: + case EggTexture::QL_default: + return ""; + + case EggTexture::QL_fastest: + return "f"; + + case EggTexture::QL_normal: + return "n"; + + case EggTexture::QL_best: + return "b"; + } + return ""; +} + +/** + * Returns a short string representing whether the color and/or alpha type has + * been specified or not. + */ +string TextureProperties:: +get_type_string(PNMFileType *color_type, PNMFileType *alpha_type) { + if (color_type == nullptr) { + return ""; + } + if (alpha_type == nullptr) { + return "c"; + } + return "a"; +} + +/** + * Returns the EggTexture format which is the more specific of the two. + */ +EggTexture::Format TextureProperties:: +union_format(EggTexture::Format a, EggTexture::Format b) { + switch (a) { + case EggTexture::F_unspecified: + return b; + + case EggTexture::F_rgba: + switch (b) { + case EggTexture::F_rgbm: + case EggTexture::F_rgba12: + case EggTexture::F_rgba8: + case EggTexture::F_rgba4: + case EggTexture::F_rgba5: + case EggTexture::F_red: + case EggTexture::F_green: + case EggTexture::F_blue: + case EggTexture::F_alpha: + return b; + + default: + return a; + }; + + case EggTexture::F_rgb: + if (b != EggTexture::F_unspecified) { + return b; + } + return a; + + default: + return a; + } +} + +/** + * Returns the EggTexture filter type which is the more specific of the two. + */ +EggTexture::FilterType TextureProperties:: +union_filter(EggTexture::FilterType a, EggTexture::FilterType b) { + if ((int)a < (int)b) { + return b; + } else { + return a; + } +} + +/** + * Returns the EggTexture quality level which is the more specific of the two. + */ +EggTexture::QualityLevel TextureProperties:: +union_quality_level(EggTexture::QualityLevel a, EggTexture::QualityLevel b) { + if ((int)a < (int)b) { + return b; + } else { + return a; + } +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void TextureProperties:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void TextureProperties:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + datagram.add_bool(_got_num_channels); + datagram.add_int32(_num_channels); + datagram.add_int32(_effective_num_channels); + datagram.add_int32((int)_format); + datagram.add_bool(_force_format); + datagram.add_bool(_generic_format); + datagram.add_bool(_keep_format); + datagram.add_int32((int)_minfilter); + datagram.add_int32((int)_magfilter); + datagram.add_int32((int)_quality_level); + datagram.add_int32(_anisotropic_degree); + datagram.add_bool(_srgb); + writer->write_pointer(datagram, _color_type); + writer->write_pointer(datagram, _alpha_type); +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int TextureProperties:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int index = TypedWritable::complete_pointers(p_list, manager); + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_color_type, p_list[index], index); + } + index++; + + if (p_list[index] != nullptr) { + DCAST_INTO_R(_alpha_type, p_list[index], index); + } + index++; + + return index; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *TextureProperties:: +make_from_bam(const FactoryParams ¶ms) { + TextureProperties *me = new TextureProperties; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void TextureProperties:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + _got_num_channels = scan.get_bool(); + _num_channels = scan.get_int32(); + _effective_num_channels = _num_channels; + if (Palettizer::_read_pi_version >= 9) { + _effective_num_channels = scan.get_int32(); + } + _format = (EggTexture::Format)scan.get_int32(); + _force_format = scan.get_bool(); + _generic_format = false; + if (Palettizer::_read_pi_version >= 9) { + _generic_format = scan.get_bool(); + } + _keep_format = false; + if (Palettizer::_read_pi_version >= 13) { + _keep_format = scan.get_bool(); + } + _minfilter = (EggTexture::FilterType)scan.get_int32(); + _magfilter = (EggTexture::FilterType)scan.get_int32(); + if (Palettizer::_read_pi_version >= 18) { + _quality_level = (EggTexture::QualityLevel)scan.get_int32(); + } + _anisotropic_degree = scan.get_int32(); + + if (Palettizer::_read_pi_version >= 21) { + _srgb = scan.get_bool(); + } + + manager->read_pointer(scan); // _color_type + manager->read_pointer(scan); // _alpha_type +} diff --git a/pandatool/src/palettizer/textureProperties.h b/pandatool/src/palettizer/textureProperties.h new file mode 100644 index 00000000..f143bdcb --- /dev/null +++ b/pandatool/src/palettizer/textureProperties.h @@ -0,0 +1,117 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureProperties.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef TEXTUREPROPERTIES_H +#define TEXTUREPROPERTIES_H + +#include "pandatoolbase.h" + +#include "eggTexture.h" +#include "typedWritable.h" + +class PNMFileType; +class FactoryParams; + +/** + * This is the set of characteristics of a texture that, if different from + * another texture, prevent the two textures from sharing a PaletteImage. It + * includes properties such as mipmapping, number of channels, etc. + */ +class TextureProperties : public TypedWritable { +public: + TextureProperties(); + TextureProperties(const TextureProperties ©); + void operator = (const TextureProperties ©); + + void clear_basic(); + + bool has_num_channels() const; + int get_num_channels() const; + void set_num_channels(int num_channels); + void force_grayscale(); + void force_nonalpha(); + bool uses_alpha() const; + + std::string get_string() const; + void update_properties(const TextureProperties &other); + void fully_define(); + + void update_egg_tex(EggTexture *egg_tex) const; + bool egg_properties_match(const TextureProperties &other) const; + + bool operator < (const TextureProperties &other) const; + bool operator == (const TextureProperties &other) const; + bool operator != (const TextureProperties &other) const; + + EggTexture::Format _format; + bool _force_format; // true when format has been explicitly specified + bool _generic_format; // true if 'generic' keyword, meaning rgba8 -> rgba. + bool _keep_format; // true if 'keep-format' keyword. + EggTexture::FilterType _minfilter, _magfilter; + EggTexture::QualityLevel _quality_level; + int _anisotropic_degree; + PNMFileType *_color_type; + PNMFileType *_alpha_type; + bool _srgb; + +private: + static std::string get_format_string(EggTexture::Format format); + static std::string get_filter_string(EggTexture::FilterType filter_type); + static std::string get_anisotropic_degree_string(int aniso_degree); + static std::string get_quality_level_string(EggTexture::QualityLevel quality_level); + static std::string get_type_string(PNMFileType *color_type, + PNMFileType *alpha_type); + + static EggTexture::Format union_format(EggTexture::Format a, + EggTexture::Format b); + + static EggTexture::FilterType union_filter(EggTexture::FilterType a, + EggTexture::FilterType b); + static EggTexture::QualityLevel union_quality_level(EggTexture::QualityLevel a, + EggTexture::QualityLevel b); + + bool _got_num_channels; + int _num_channels; + int _effective_num_channels; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + +public: + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "TextureProperties", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/palettizer/textureReference.cxx b/pandatool/src/palettizer/textureReference.cxx new file mode 100644 index 00000000..3b6c2696 --- /dev/null +++ b/pandatool/src/palettizer/textureReference.cxx @@ -0,0 +1,901 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureReference.cxx + * @author drose + * @date 2000-11-29 + */ + +#include "textureReference.h" +#include "textureImage.h" +#include "paletteImage.h" +#include "sourceTextureImage.h" +#include "destTextureImage.h" +#include "texturePlacement.h" +#include "palettizer.h" +#include "eggFile.h" + +#include "indent.h" +#include "eggTexture.h" +#include "eggData.h" +#include "eggGroupNode.h" +#include "eggGroup.h" +#include "eggNurbsSurface.h" +#include "eggVertexPool.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "bamReader.h" +#include "bamWriter.h" +#include "string_utils.h" + +#include + +using std::max; +using std::min; +using std::string; + +TypeHandle TextureReference::_type_handle; + +/** + * + */ +TextureReference:: +TextureReference() { + _egg_file = nullptr; + _egg_tex = nullptr; + _tex_mat = LMatrix3d::ident_mat(); + _inv_tex_mat = LMatrix3d::ident_mat(); + _source_texture = nullptr; + _placement = nullptr; + _uses_alpha = false; + _any_uvs = false; + _min_uv.set(0.0, 0.0); + _max_uv.set(0.0, 0.0); + _wrap_u = EggTexture::WM_unspecified; + _wrap_v = EggTexture::WM_unspecified; +} + +/** + * + */ +TextureReference:: +~TextureReference() { + clear_placement(); +} + +/** + * Sets up the TextureReference using information extracted from an egg file. + */ +void TextureReference:: +from_egg(EggFile *egg_file, EggData *data, EggTexture *egg_tex) { + _egg_file = egg_file; + _egg_tex = egg_tex; + _egg_data = data; + _tref_name = egg_tex->get_name(); + + if (_egg_tex->has_transform2d()) { + _tex_mat = _egg_tex->get_transform2d(); + if (!_inv_tex_mat.invert_from(_tex_mat)) { + _inv_tex_mat = LMatrix3d::ident_mat(); + } + } else { + _tex_mat = LMatrix3d::ident_mat(); + _inv_tex_mat = LMatrix3d::ident_mat(); + } + + Filename filename = _egg_tex->get_filename(); + Filename alpha_filename; + if (_egg_tex->has_alpha_filename()) { + alpha_filename = _egg_tex->get_alpha_filename(); + } + int alpha_file_channel = _egg_tex->get_alpha_file_channel(); + + _properties._format = _egg_tex->get_format(); + _properties._minfilter = _egg_tex->get_minfilter(); + _properties._magfilter = _egg_tex->get_magfilter(); + _properties._quality_level = _egg_tex->get_quality_level(); + _properties._anisotropic_degree = _egg_tex->get_anisotropic_degree(); + + string name = filename.get_basename_wo_extension(); + TextureImage *texture = pal->get_texture(name); + if (texture->get_name() != name) { + nout << "Texture name conflict: \"" << name + << "\" conflicts with existing texture named \"" + << texture->get_name() << "\".\n"; + + // Make this a hard error; refuse to do anything else until the user fixes + // it. Case conflicts can be very bad, especially if CVS is involved on a + // Windows machine. + exit(1); + } + _source_texture = texture->get_source(filename, alpha_filename, + alpha_file_channel); + _source_texture->update_properties(_properties); + + _uses_alpha = false; + EggRenderMode::AlphaMode alpha_mode = _egg_tex->get_alpha_mode(); + if (alpha_mode == EggRenderMode::AM_unspecified) { + if (_source_texture->get_size()) { + _uses_alpha = + _egg_tex->has_alpha_channel(_source_texture->get_num_channels()); + } + + } else if (alpha_mode == EggRenderMode::AM_off) { + _uses_alpha = false; + + } else { + _uses_alpha = true; + } + + get_uv_range(_egg_data, pal->_remap_uv); + + _wrap_u = _egg_tex->determine_wrap_u(); + _wrap_v = _egg_tex->determine_wrap_v(); +} + +/** + * Sets up the pointers within the TextureReference to the same egg file + * pointers indicated by the other TextureReference object, without changing + * any of the other internal data stored here regarding the egg structures. + * This is intended for use when we have already shown that the two + * TextureReferences describe equivalent data. + */ +void TextureReference:: +from_egg_quick(const TextureReference &other) { + nassertv(_tref_name == other._tref_name); + _egg_file = other._egg_file; + _egg_tex = other._egg_tex; + _egg_data = other._egg_data; +} + +/** + * Called to indicate that the EggData previously passed to from_egg() is + * about to be deallocated, and all of its pointers should be cleared. + */ +void TextureReference:: +release_egg_data() { + _egg_tex = nullptr; + _egg_data = nullptr; +} + +/** + * After an EggData has previously been released via release_egg_data(), this + * can be called to indicate that the egg file has been reloaded and we should + * assign the indicated pointers. + */ +void TextureReference:: +rebind_egg_data(EggData *data, EggTexture *egg_tex) { + nassertv(_tref_name == egg_tex->get_name()); + _egg_data = data; + _egg_tex = egg_tex; +} + +/** + * Returns the EggFile that references this texture. + */ +EggFile *TextureReference:: +get_egg_file() const { + return _egg_file; +} + +/** + * Returns the SourceTextureImage that this object refers to. + */ +SourceTextureImage *TextureReference:: +get_source() const { + return _source_texture; +} + +/** + * Returns the TextureImage that this object refers to. + */ +TextureImage *TextureReference:: +get_texture() const { + nassertr(_source_texture != nullptr, nullptr); + return _source_texture->get_texture(); +} + +/** + * Returns the name of the EggTexture entry that references this texture. + */ +const string &TextureReference:: +get_tref_name() const { + return _tref_name; +} + +/** + * Defines an ordering of TextureReference pointers in alphabetical order by + * their tref name. + */ +bool TextureReference:: +operator < (const TextureReference &other) const { + return _tref_name < other._tref_name; +} + +/** + * Returns true if this TextureReference actually uses the texture on + * geometry, with UV's and everything, or false otherwise. Strictly speaking, + * this should always return true. + */ +bool TextureReference:: +has_uvs() const { + return _any_uvs; +} + +/** + * Returns the minimum UV coordinate in use for the texture by this reference. + */ +const LTexCoordd &TextureReference:: +get_min_uv() const { + nassertr(_any_uvs, _min_uv); + return _min_uv; +} + +/** + * Returns the maximum UV coordinate in use for the texture by this reference. + */ +const LTexCoordd &TextureReference:: +get_max_uv() const { + nassertr(_any_uvs, _max_uv); + return _max_uv; +} + +/** + * Returns the specification for the wrapping in the U direction. + */ +EggTexture::WrapMode TextureReference:: +get_wrap_u() const { + return _wrap_u; +} + +/** + * Returns the specification for the wrapping in the V direction. + */ +EggTexture::WrapMode TextureReference:: +get_wrap_v() const { + return _wrap_v; +} + +/** + * Returns true if all essential properties of this TextureReference are the + * same as that of the other, or false if any of them differ. This is useful + * when reading a new egg file and comparing its references to its previously- + * defined references. + */ +bool TextureReference:: +is_equivalent(const TextureReference &other) const { + if (_source_texture != other._source_texture) { + return false; + } + if (!_properties.egg_properties_match(other._properties)) { + return false; + } + if (_uses_alpha != other._uses_alpha) { + return false; + } + if (_any_uvs != other._any_uvs) { + return false; + } + if (_wrap_u != other._wrap_u || + _wrap_v != other._wrap_v) { + return false; + } + if (_any_uvs) { + if (!_min_uv.almost_equal(other._min_uv, 0.00001)) { + return false; + } + if (!_max_uv.almost_equal(other._max_uv, 0.00001)) { + return false; + } + } + if (!_tex_mat.almost_equal(other._tex_mat, 0.00001)) { + return false; + } + + return true; +} + +/** + * Sets the particular TexturePlacement that is appropriate for this egg file. + * This is called by EggFile::choose_placements(). + */ +void TextureReference:: +set_placement(TexturePlacement *placement) { + if (_placement != placement) { + if (_placement != nullptr) { + // Remove our reference from the old placement object. + _placement->remove_egg(this); + } + _placement = placement; + if (_placement != nullptr) { + // Add our reference to the new placement object. + _placement->add_egg(this); + } + } +} + +/** + * Removes any reference to a TexturePlacement. + */ +void TextureReference:: +clear_placement() { + set_placement(nullptr); +} + +/** + * Returns the particular TexturePlacement that is appropriate for this egg + * file. This will not be filled in until EggFile::choose_placements() has + * been called. + */ +TexturePlacement *TextureReference:: +get_placement() const { + return _placement; +} + +/** + * Marks the egg file that shares this reference as stale. + */ +void TextureReference:: +mark_egg_stale() { + if (_egg_file != nullptr) { + _egg_file->mark_stale(); + } +} + +/** + * Updates the egg file with all the relevant information to reference the + * texture in its new home, wherever that might be. + */ +void TextureReference:: +update_egg() { + if (_egg_tex == nullptr) { + // Not much we can do if we don't have an actual egg file to reference. + return; + } + + if (_placement == nullptr) { + // Nor if we don't have an actual placement yet. This is possible if the + // egg was assigned to the "null" group, and the texture hasn't been re- + // assigned yet. + return; + } + + TextureImage *texture = get_texture(); + if (texture != nullptr) { + // Make sure the alpha mode is set according to what the texture image + // wants. + if (texture->has_num_channels() && + !_egg_tex->has_alpha_channel(texture->get_num_channels())) { + // The egg file doesn't want to use the alpha on the texture; leave it + // unspecified so the egg loader can figure out whether to enable alpha + // or not based on the object color. + _egg_tex->set_alpha_mode(EggRenderMode::AM_unspecified); + + } else { + // The egg file does want alpha, so get the alpha mode from the texture. + EggRenderMode::AlphaMode am = texture->get_alpha_mode(); + if (am != EggRenderMode::AM_unspecified) { + _egg_tex->set_alpha_mode(am); + } + } + + // Also make sure the wrap mode is set properly. + if (texture->get_txa_wrap_u() != EggTexture::WM_unspecified) { + _egg_tex->set_wrap_u(texture->get_txa_wrap_u()); + } + if (texture->get_txa_wrap_v() != EggTexture::WM_unspecified) { + _egg_tex->set_wrap_v(texture->get_txa_wrap_v()); + } + } + + // We check for an OmitReason of OR_none, rather than asking is_placed(), + // because in this case we don't want to consider an OR_solitary texture as + // having been placed. + if (_placement->get_omit_reason() == OR_unknown) { + // The texture doesn't even exist. We can't update the egg to point to + // any meaningful path; just leave it pointing to the source texture's + // basename. Maybe it will be found along the texture path later. + Filename orig_filename = _egg_tex->get_filename(); + texture->update_egg_tex(_egg_tex); + _egg_tex->set_filename(orig_filename.get_basename()); + return; + } + if (_placement->get_omit_reason() != OR_none) { + // The texture exists but is not on a palette. This is the easy case; we + // simply have to update the texture reference to the new texture + // location. + DestTextureImage *dest = _placement->get_dest(); + nassertv(dest != nullptr); + dest->update_egg_tex(_egg_tex); + return; + } + + // The texture *does* appear on a palette. This means we need to not only + // update the texture reference, but also adjust the UV's. In most cases, + // we can do this by simply applying a texture matrix to the reference. + PaletteImage *image = _placement->get_image(); + nassertv(image != nullptr); + + image->update_egg_tex(_egg_tex); + + // Palette images never wrap, so the wrap mode doesn't matter. We let this + // default to unspecified, which means the images will wrap by default, + // which is the fastest mode for tinydisplay anyway. + _egg_tex->set_wrap_mode(EggTexture::WM_unspecified); + _egg_tex->set_wrap_u(EggTexture::WM_unspecified); + _egg_tex->set_wrap_v(EggTexture::WM_unspecified); + + LMatrix3d new_tex_mat; + _placement->compute_tex_matrix(new_tex_mat); + + // Compose the new texture matrix with whatever matrix was already there, if + // any. + _egg_tex->set_transform2d(_tex_mat * new_tex_mat); + + // Finally, go back and actually adjust the UV's to match what we claimed + // they could be. + if (_egg_tex->get_tex_gen() == EggTexture::TG_unspecified) { + update_uv_range(_egg_data, pal->_remap_uv); + } +} + +/** + * Applies the texture properties as read from the egg file to the source + * image's properties. This updates the source image with the now-known + * properties indicated with in the tref block of the egg file. + */ +void TextureReference:: +apply_properties_to_source() { + nassertv(_source_texture != nullptr); + _source_texture->update_properties(_properties); +} + +/** + * + */ +void TextureReference:: +output(std::ostream &out) const { + out << *_source_texture; +} + +/** + * + */ +void TextureReference:: +write(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << get_texture()->get_name(); + + if (_uses_alpha) { + out << " (uses alpha)"; + } + + if (_any_uvs) { + // Compute the fraction of the image that is covered by the UV's minmax + // rectangle. + LTexCoordd box = _max_uv - _min_uv; + double area = box[0] * box[1]; + + out << " coverage " << area; + } + + if (_wrap_u != EggTexture::WM_unspecified || + _wrap_v != EggTexture::WM_unspecified) { + if (_wrap_u != _wrap_v) { + out << " (" << _wrap_u << ", " << _wrap_v << ")"; + } else { + out << " " << _wrap_u; + } + } + + if (_properties._format != EggTexture::F_unspecified) { + out << " " << _properties._format; + } + + switch (_properties._minfilter) { + case EggTexture::FT_nearest_mipmap_nearest: + case EggTexture::FT_linear_mipmap_nearest: + case EggTexture::FT_nearest_mipmap_linear: + case EggTexture::FT_linear_mipmap_linear: + out << " mipmap"; + break; + + default: + break; + } + + if(_properties._anisotropic_degree>1) { + out << " aniso " << _properties._anisotropic_degree; + } + + out << "\n"; +} + + +/** + * Checks the geometry in the egg file to see what range of UV's are requested + * for this particular texture reference. + * + * If pal->_remap_uv is not RU_never, this will also attempt to remap the UV's + * found so that the midpoint lies in the unit square (0,0) - (1,1), in the + * hopes of maximizing overlap of UV coordinates between different polygons. + * However, the hypothetical translations are not actually applied to the egg + * file at this point (because we might decide not to place the texture in a + * palette); they will actually be applied when update_uv_range(), below, is + * called later. + * + * The return value is true if the search should continue, or false if it + * should abort prematurely. + */ +bool TextureReference:: +get_uv_range(EggGroupNode *group, Palettizer::RemapUV remap) { + if (group->is_of_type(EggGroup::get_class_type())) { + EggGroup *egg_group; + DCAST_INTO_R(egg_group, group, false); + + if (egg_group->get_dart_type() != EggGroup::DT_none) { + // If it's a character, we might change the kind of remapping we do. + remap = pal->_remap_char_uv; + } + } + + bool group_any_uvs = false; + LTexCoordd group_min_uv, group_max_uv; + + EggGroupNode::iterator ci; + for (ci = group->begin(); ci != group->end(); ci++) { + EggNode *child = (*ci); + if (child->is_of_type(EggNurbsSurface::get_class_type())) { + EggNurbsSurface *nurbs = DCAST(EggNurbsSurface, child); + if (nurbs->has_texture(_egg_tex)) { + // Here's a NURBS surface that references the texture. Unlike other + // kinds of geometries, NURBS don't store UV's; they're implicit in + // the surface. NURBS UV's will always run in the range (0, 0) - (1, + // 1). However, we do need to apply the texture matrix. + + // We also don't count the NURBS surfaces in with the group's UV's, + // because we can't adjust the UV's on a NURBS, so counting them up + // would be misleading (the reason we count up the group UV's is so we + // can consider adjusting them later). Instead, we just accumulate + // the NURBS UV's directly into our total. + collect_nominal_uv_range(); + } + + } else if (child->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *geom = DCAST(EggPrimitive, child); + if (geom->has_texture(_egg_tex)) { + // Here's a piece of geometry that references this texture. Walk + // through its vertices and get its UV's. + + if (_egg_tex->get_tex_gen() != EggTexture::TG_unspecified) { + // If the texture has a TexGen mode, we don't check the UV range on + // the model, since that doesn't matter. Instead, we assume the + // texture is used in the range (0, 0) - (1, 1), which will be true + // for a sphere map, although the effective range is a little less + // clear for the TG_world_position and similar modes. + collect_nominal_uv_range(); + + // In fact, now we can return, having found at least one model that + // references the texture; there's no need to search further. + return false; + + } else { + LTexCoordd geom_min_uv, geom_max_uv; + + if (get_geom_uvs(geom, geom_min_uv, geom_max_uv)) { + if (remap == Palettizer::RU_poly) { + LVector2d trans = translate_uv(geom_min_uv, geom_max_uv); + geom_min_uv += trans; + geom_max_uv += trans; + } + collect_uv(group_any_uvs, group_min_uv, group_max_uv, + geom_min_uv, geom_max_uv); + } + } + } + + } else if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *cg = DCAST(EggGroupNode, child); + if (!get_uv_range(cg, remap)) { + return false; + } + } + } + + if (group_any_uvs) { + if (remap == Palettizer::RU_group) { + LVector2d trans = translate_uv(group_min_uv, group_max_uv); + group_min_uv += trans; + group_max_uv += trans; + } + collect_uv(_any_uvs, _min_uv, _max_uv, group_min_uv, group_max_uv); + } + + return true; +} + +/** + * Actually applies the UV translates that were assumed in the previous call + * to get_uv_range(). + */ +void TextureReference:: +update_uv_range(EggGroupNode *group, Palettizer::RemapUV remap) { + if (group->is_of_type(EggGroup::get_class_type())) { + EggGroup *egg_group; + DCAST_INTO_V(egg_group, group); + + if (egg_group->get_dart_type() != EggGroup::DT_none) { + // If it's a character, we might change the kind of remapping we do. + remap = pal->_remap_char_uv; + } + } + + bool group_any_uvs = false; + LTexCoordd group_min_uv, group_max_uv; + + EggGroupNode::iterator ci; + for (ci = group->begin(); ci != group->end(); ci++) { + EggNode *child = (*ci); + if (child->is_of_type(EggNurbsSurface::get_class_type())) { + // We do nothing at this point for a Nurbs. Nothing we can do about + // these things. + + } else if (child->is_of_type(EggPrimitive::get_class_type())) { + if (remap != Palettizer::RU_never) { + EggPrimitive *geom = DCAST(EggPrimitive, child); + if (geom->has_texture(_egg_tex)) { + LTexCoordd geom_min_uv, geom_max_uv; + + if (get_geom_uvs(geom, geom_min_uv, geom_max_uv)) { + if (remap == Palettizer::RU_poly) { + LVector2d trans = translate_uv(geom_min_uv, geom_max_uv); + trans = trans * _inv_tex_mat; + if (!trans.almost_equal(LVector2d::zero())) { + translate_geom_uvs(geom, trans); + } + } else { + collect_uv(group_any_uvs, group_min_uv, group_max_uv, + geom_min_uv, geom_max_uv); + } + } + } + } + + } else if (child->is_of_type(EggGroupNode::get_class_type())) { + EggGroupNode *cg = DCAST(EggGroupNode, child); + update_uv_range(cg, remap); + } + } + + if (group_any_uvs && remap == Palettizer::RU_group) { + LVector2d trans = translate_uv(group_min_uv, group_max_uv); + trans = trans * _inv_tex_mat; + if (!trans.almost_equal(LVector2d::zero())) { + for (ci = group->begin(); ci != group->end(); ci++) { + EggNode *child = (*ci); + if (child->is_of_type(EggPrimitive::get_class_type())) { + EggPrimitive *geom = DCAST(EggPrimitive, child); + if (geom->has_texture(_egg_tex)) { + translate_geom_uvs(geom, trans); + } + } + } + } + } +} + +/** + * Determines the minimum and maximum UV range for a particular primitive. + * Returns true if it has any UV's, false otherwise. + */ +bool TextureReference:: +get_geom_uvs(EggPrimitive *geom, + LTexCoordd &geom_min_uv, LTexCoordd &geom_max_uv) { + string uv_name = _egg_tex->get_uv_name(); + bool geom_any_uvs = false; + + EggPrimitive::iterator pi; + for (pi = geom->begin(); pi != geom->end(); ++pi) { + EggVertex *vtx = (*pi); + if (vtx->has_uv(uv_name)) { + LTexCoordd uv = vtx->get_uv(uv_name) * _tex_mat; + collect_uv(geom_any_uvs, geom_min_uv, geom_max_uv, uv, uv); + } + } + + return geom_any_uvs; +} + +/** + * Applies the indicated translation to each UV in the primitive. + */ +void TextureReference:: +translate_geom_uvs(EggPrimitive *geom, const LTexCoordd &trans) const { + string uv_name = _egg_tex->get_uv_name(); + + EggPrimitive::iterator pi; + for (pi = geom->begin(); pi != geom->end(); ++pi) { + EggVertex *vtx = (*pi); + if (vtx->has_uv(uv_name)) { + EggVertex vtx_copy(*vtx); + vtx_copy.set_uv(uv_name, vtx_copy.get_uv(uv_name) + trans); + EggVertex *new_vtx = vtx->get_pool()->create_unique_vertex(vtx_copy); + + if (new_vtx->gref_size() != vtx->gref_size()) { + new_vtx->copy_grefs_from(*vtx); + } + + geom->replace(pi, new_vtx); + } + } +} + +/** + * Updates _any_uvs, _min_uv, and _max_uv with the range (0, 0) - (1, 1), + * adjusted by the texture matrix. + */ +void TextureReference:: +collect_nominal_uv_range() { + static const int num_nurbs_uvs = 4; + static LTexCoordd nurbs_uvs[num_nurbs_uvs] = { + LTexCoordd(0.0, 0.0), + LTexCoordd(0.0, 1.0), + LTexCoordd(1.0, 1.0), + LTexCoordd(1.0, 0.0) + }; + + for (int i = 0; i < num_nurbs_uvs; i++) { + LTexCoordd uv = nurbs_uvs[i] * _tex_mat; + collect_uv(_any_uvs, _min_uv, _max_uv, uv, uv); + } +} + +/** + * Updates any_uvs, min_uv, and max_uv with the indicated min and max UV's + * already determined. + */ +void TextureReference:: +collect_uv(bool &any_uvs, LTexCoordd &min_uv, LTexCoordd &max_uv, + const LTexCoordd &got_min_uv, const LTexCoordd &got_max_uv) { + if (any_uvs) { + min_uv.set(min(min_uv[0], got_min_uv[0]), + min(min_uv[1], got_min_uv[1])); + max_uv.set(max(max_uv[0], got_max_uv[0]), + max(max_uv[1], got_max_uv[1])); + } else { + // The first UV. + min_uv = got_min_uv; + max_uv = got_max_uv; + any_uvs = true; + } +} + +/** + * Returns the needed adjustment to translate the given bounding box so that + * its center lies in the unit square (0,0) - (1,1). + */ +LVector2d TextureReference:: +translate_uv(const LTexCoordd &min_uv, const LTexCoordd &max_uv) { + LTexCoordd center = (min_uv + max_uv) / 2; + return LVector2d(-floor(center[0]), -floor(center[1])); +} + +/** + * Registers the current object as something that can be read from a Bam file. + */ +void TextureReference:: +register_with_read_factory() { + BamReader::get_factory()-> + register_factory(get_class_type(), make_from_bam); +} + +/** + * Fills the indicated datagram up with a binary representation of the current + * object, in preparation for writing to a Bam file. + */ +void TextureReference:: +write_datagram(BamWriter *writer, Datagram &datagram) { + TypedWritable::write_datagram(writer, datagram); + writer->write_pointer(datagram, _egg_file); + + // We don't write _egg_tex or _egg_data; that's specific to the session. + + datagram.add_string(_tref_name); + + _tex_mat.write_datagram(datagram); + _inv_tex_mat.write_datagram(datagram); + + writer->write_pointer(datagram, _source_texture); + writer->write_pointer(datagram, _placement); + + datagram.add_bool(_uses_alpha); + datagram.add_bool(_any_uvs); + datagram.add_float64(_min_uv[0]); + datagram.add_float64(_min_uv[1]); + datagram.add_float64(_max_uv[0]); + datagram.add_float64(_max_uv[1]); + datagram.add_int32((int)_wrap_u); + datagram.add_int32((int)_wrap_v); + _properties.write_datagram(writer, datagram); +} + +/** + * Called after the object is otherwise completely read from a Bam file, this + * function's job is to store the pointers that were retrieved from the Bam + * file for each pointer object written. The return value is the number of + * pointers processed from the list. + */ +int TextureReference:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = TypedWritable::complete_pointers(p_list, manager); + + if (p_list[pi] != nullptr) { + DCAST_INTO_R(_egg_file, p_list[pi], pi); + } + pi++; + + if (p_list[pi] != nullptr) { + DCAST_INTO_R(_source_texture, p_list[pi], pi); + } + pi++; + + if (p_list[pi] != nullptr) { + DCAST_INTO_R(_placement, p_list[pi], pi); + } + pi++; + + pi += _properties.complete_pointers(p_list + pi, manager); + + return pi; +} + +/** + * This method is called by the BamReader when an object of this type is + * encountered in a Bam file; it should allocate and return a new object with + * all the data read. + */ +TypedWritable *TextureReference:: +make_from_bam(const FactoryParams ¶ms) { + TextureReference *me = new TextureReference; + DatagramIterator scan; + BamReader *manager; + + parse_params(params, scan, manager); + me->fillin(scan, manager); + return me; +} + +/** + * Reads the binary data from the given datagram iterator, which was written + * by a previous call to write_datagram(). + */ +void TextureReference:: +fillin(DatagramIterator &scan, BamReader *manager) { + TypedWritable::fillin(scan, manager); + manager->read_pointer(scan); // _egg_file + + if (Palettizer::_read_pi_version >= 11) { + _tref_name = scan.get_string(); + } + + _tex_mat.read_datagram(scan); + _inv_tex_mat.read_datagram(scan); + + manager->read_pointer(scan); // _source_texture + manager->read_pointer(scan); // _placement + + _uses_alpha = scan.get_bool(); + _any_uvs = scan.get_bool(); + _min_uv[0] = scan.get_float64(); + _min_uv[1] = scan.get_float64(); + _max_uv[0] = scan.get_float64(); + _max_uv[1] = scan.get_float64(); + _wrap_u = (EggTexture::WrapMode)scan.get_int32(); + _wrap_v = (EggTexture::WrapMode)scan.get_int32(); + _properties.fillin(scan, manager); +} diff --git a/pandatool/src/palettizer/textureReference.h b/pandatool/src/palettizer/textureReference.h new file mode 100644 index 00000000..720994c9 --- /dev/null +++ b/pandatool/src/palettizer/textureReference.h @@ -0,0 +1,143 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureReference.h + * @author drose + * @date 2000-11-28 + */ + +#ifndef TEXTUREREFERENCE_H +#define TEXTUREREFERENCE_H + +#include "pandatoolbase.h" + +#include "textureProperties.h" +#include "palettizer.h" + +#include "luse.h" +#include "typedWritable.h" + +class TextureImage; +class SourceTextureImage; +class Filename; +class EggFile; +class EggData; +class EggTexture; +class EggGroupNode; +class EggPrimitive; +class TexturePlacement; + +/** + * This is the particular reference of a texture filename by an egg file. It + * also includes information about the way in which the egg file uses the + * texture; e.g. does it repeat. + */ +class TextureReference : public TypedWritable { +public: + TextureReference(); + ~TextureReference(); + + void from_egg(EggFile *egg_file, EggData *data, EggTexture *egg_tex); + void from_egg_quick(const TextureReference &other); + void release_egg_data(); + void rebind_egg_data(EggData *data, EggTexture *egg_tex); + + EggFile *get_egg_file() const; + SourceTextureImage *get_source() const; + TextureImage *get_texture() const; + const std::string &get_tref_name() const; + + bool operator < (const TextureReference &other) const; + + bool has_uvs() const; + const LTexCoordd &get_min_uv() const; + const LTexCoordd &get_max_uv() const; + + EggTexture::WrapMode get_wrap_u() const; + EggTexture::WrapMode get_wrap_v() const; + + bool is_equivalent(const TextureReference &other) const; + + void set_placement(TexturePlacement *placement); + void clear_placement(); + TexturePlacement *get_placement() const; + + void mark_egg_stale(); + void update_egg(); + void apply_properties_to_source(); + + void output(std::ostream &out) const; + void write(std::ostream &out, int indent_level = 0) const; + + +private: + bool get_uv_range(EggGroupNode *group, Palettizer::RemapUV remap); + void update_uv_range(EggGroupNode *group, Palettizer::RemapUV remap); + + bool get_geom_uvs(EggPrimitive *geom, + LTexCoordd &geom_min_uv, LTexCoordd &geom_max_uv); + void translate_geom_uvs(EggPrimitive *geom, const LTexCoordd &trans) const; + void collect_nominal_uv_range(); + static void collect_uv(bool &any_uvs, LTexCoordd &min_uv, LTexCoordd &max_uv, + const LTexCoordd &got_min_uv, + const LTexCoordd &got_max_uv); + static LVector2d translate_uv(const LTexCoordd &min_uv, + const LTexCoordd &max_uv); + + EggFile *_egg_file; + EggTexture *_egg_tex; + EggData *_egg_data; + + std::string _tref_name; + LMatrix3d _tex_mat, _inv_tex_mat; + SourceTextureImage *_source_texture; + TexturePlacement *_placement; + + bool _uses_alpha; + + bool _any_uvs; + LTexCoordd _min_uv, _max_uv; + EggTexture::WrapMode _wrap_u, _wrap_v; + + TextureProperties _properties; + + // The TypedWritable interface follows. +public: + static void register_with_read_factory(); + virtual void write_datagram(BamWriter *writer, Datagram &datagram); + virtual int complete_pointers(TypedWritable **p_list, + BamReader *manager); + +protected: + static TypedWritable *make_from_bam(const FactoryParams ¶ms); + void fillin(DatagramIterator &scan, BamReader *manager); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedWritable::init_type(); + register_type(_type_handle, "TextureReference", + TypedWritable::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream & +operator << (std::ostream &out, const TextureReference &ref) { + ref.output(out); + return out; +} + +#endif diff --git a/pandatool/src/palettizer/textureRequest.cxx b/pandatool/src/palettizer/textureRequest.cxx new file mode 100644 index 00000000..775c5bb5 --- /dev/null +++ b/pandatool/src/palettizer/textureRequest.cxx @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureRequest.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "textureRequest.h" +#include "palettizer.h" + +/** + * + */ +TextureRequest:: +TextureRequest() { + _got_size = false; + _got_num_channels = false; + _srgb = false; + _x_size = 0; + _y_size = 0; + _num_channels = 0; + _format = EggTexture::F_unspecified; + _force_format = false; + _generic_format = false; + _keep_format = false; + _minfilter = EggTexture::FT_unspecified; + _magfilter = EggTexture::FT_unspecified; + _anisotropic_degree = 0; + _alpha_mode = EggRenderMode::AM_unspecified; + _wrap_u = EggTexture::WM_unspecified; + _wrap_v = EggTexture::WM_unspecified; + _omit = false; + _margin = 0; + _coverage_threshold = 0.0; +} + +/** + * Sets some state up that must be set prior to reading the .txa file. + */ +void TextureRequest:: +pre_txa_file() { + _margin = pal->_margin; + _coverage_threshold = pal->_coverage_threshold; +} diff --git a/pandatool/src/palettizer/textureRequest.h b/pandatool/src/palettizer/textureRequest.h new file mode 100644 index 00000000..54162283 --- /dev/null +++ b/pandatool/src/palettizer/textureRequest.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textureRequest.h + * @author drose + * @date 2000-11-29 + */ + +#ifndef TEXTUREREQUEST_H +#define TEXTUREREQUEST_H + +#include "pandatoolbase.h" + +#include "textureProperties.h" + +#include "eggTexture.h" +#include "eggRenderMode.h" + +/** + * These are the things that a user might explicitly request to adjust on a + * texture via a line in the .txa file. + */ +class TextureRequest { +public: + TextureRequest(); + void pre_txa_file(); + + TextureProperties _properties; + + bool _got_size; + bool _got_num_channels; + int _x_size; + int _y_size; + int _num_channels; + EggTexture::Format _format; + bool _force_format; + bool _generic_format; + bool _keep_format; + bool _srgb; + EggTexture::FilterType _minfilter; + EggTexture::FilterType _magfilter; + int _anisotropic_degree; + EggRenderMode::AlphaMode _alpha_mode; + EggTexture::WrapMode _wrap_u, _wrap_v; + bool _omit; + int _margin; + double _coverage_threshold; +}; + +#endif diff --git a/pandatool/src/palettizer/txaFile.cxx b/pandatool/src/palettizer/txaFile.cxx new file mode 100644 index 00000000..cb4cb533 --- /dev/null +++ b/pandatool/src/palettizer/txaFile.cxx @@ -0,0 +1,598 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaFile.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "txaFile.h" +#include "pal_string_utils.h" +#include "palettizer.h" +#include "paletteGroup.h" +#include "textureImage.h" + +#include "pnotify.h" +#include "pnmFileTypeRegistry.h" + +using std::string; + +/** + * + */ +TxaFile:: +TxaFile() { +} + +/** + * Reads the indicated stream, and returns true if successful, or false if + * there is an error. + */ +bool TxaFile:: +read(std::istream &in, const string &filename) { + string line; + int line_number = 1; + + int ch = get_line_or_semicolon(in, line); + while (ch != EOF || !line.empty()) { + bool okflag = true; + + // Strip off the comment. + size_t hash = line.find('#'); + if (hash != string::npos) { + line = line.substr(0, hash); + } + line = trim_left(line); + if (line.empty()) { + // Empty lines are ignored. + + } else if (line[0] == ':') { + // This is a keyword line. + vector_string words; + extract_words(line, words); + if (words[0] == ":group") { + okflag = parse_group_line(words); + + } else if (words[0] == ":palette") { + okflag = parse_palette_line(words); + + } else if (words[0] == ":margin") { + okflag = parse_margin_line(words); + + } else if (words[0] == ":background") { + okflag = parse_background_line(words); + + } else if (words[0] == ":coverage") { + okflag = parse_coverage_line(words); + + } else if (words[0] == ":powertwo") { + okflag = parse_powertwo_line(words); + + } else if (words[0] == ":imagetype") { + okflag = parse_imagetype_line(words); + + } else if (words[0] == ":shadowtype") { + okflag = parse_shadowtype_line(words); + + } else if (words[0] == ":round") { + okflag = parse_round_line(words); + + } else if (words[0] == ":remap") { + okflag = parse_remap_line(words); + + } else if (words[0] == ":cutout") { + okflag = parse_cutout_line(words); + + } else if (words[0] == ":textureswap") { + okflag = parse_textureswap_line(words); + + } else { + nout << "Invalid keyword " << words[0] << "\n"; + okflag = false; + } + + } else { + _lines.push_back(TxaLine()); + TxaLine &txa_line = _lines.back(); + + okflag = txa_line.parse(line); + } + + if (!okflag) { + nout << "Error on line " << line_number << " of " << filename << "\n"; + return false; + } + if (ch == '\n') { + line_number++; + } + ch = get_line_or_semicolon(in, line); + } + + if (!in.eof()) { + nout << "I/O error reading " << filename << "\n"; + return false; + } + + return true; +} + +/** + * Searches for a matching line in the .txa file for the given egg file and + * applies its specifications. If a match is found, returns true; otherwise, + * returns false. Also returns false if all the matching lines for the egg + * file include the keyword "cont". + */ +bool TxaFile:: +match_egg(EggFile *egg_file) const { + Lines::const_iterator li; + for (li = _lines.begin(); li != _lines.end(); ++li) { + if ((*li).match_egg(egg_file)) { + return true; + } + } + + return false; +} + +/** + * Searches for a matching line in the .txa file for the given texture and + * applies its specifications. If a match is found, returns true; otherwise, + * returns false. Also returns false if all the matching lines for the + * texture include the keyword "cont". + */ +bool TxaFile:: +match_texture(TextureImage *texture) const { + Lines::const_iterator li; + for (li = _lines.begin(); li != _lines.end(); ++li) { + if ((*li).match_texture(texture)) { + return true; + } + } + + return false; +} + +/** + * Outputs a representation of the lines that were read in to the indicated + * output stream. This is primarily useful for debugging. + */ +void TxaFile:: +write(std::ostream &out) const { + Lines::const_iterator li; + for (li = _lines.begin(); li != _lines.end(); ++li) { + out << (*li) << "\n"; + } +} + +/** + * Reads the next line, or the next semicolon-delimited phrase, from the + * indicated input stream. Returns the character that marks the end of the + * line, or EOF if the end of file has been reached. + */ +int TxaFile:: +get_line_or_semicolon(std::istream &in, string &line) { + line = string(); + int ch = in.get(); + char semicolon = ';'; + + while (ch != EOF && ch != '\n' && ch != semicolon) { + if (ch == '#') { + // We don't consider a semicolon within a comment to be a line break. + semicolon = EOF; + } + line += ch; + ch = in.get(); + } + + return ch; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":group" and + * indicates the relationships between one or more groups. + */ +bool TxaFile:: +parse_group_line(const vector_string &words) { + vector_string::const_iterator wi; + wi = words.begin(); + assert (wi != words.end()); + ++wi; + + const string &group_name = (*wi); + PaletteGroup *group = pal->get_palette_group(group_name); + ++wi; + + enum State { + S_none, + S_on, + S_includes, + S_dir, + S_margin, + }; + State state = S_none; + + bool first_on = true; + + while (wi != words.end()) { + const string &word = (*wi); + if (word == "with") { + // Deprecated keyword: "with" means the same thing as "on". + state = S_on; + + } else if (word == "on") { + state = S_on; + + } else if (word == "includes") { + state = S_includes; + + } else if (word == "dir") { + state = S_dir; + + } else if (word == "margin") { + state = S_margin; + + } else { + switch (state) { + case S_none: + nout << "Invalid keyword: " << word << "\n"; + return false; + + case S_on: + { + PaletteGroup *on_group = pal->get_palette_group(word); + if (first_on) { + if (!group->has_dirname() && on_group->has_dirname()) { + group->set_dirname(on_group->get_dirname()); + } + first_on = false; + } + group->group_with(on_group); + } + break; + + case S_includes: + pal->get_palette_group(word)->group_with(group); + break; + + case S_dir: + group->set_dirname(word); + state = S_none; + break; + + case S_margin: + int margin_override; + if (string_to_int(word, margin_override)) { + group->set_margin_override(margin_override); + } + state = S_none; + break; + } + + } + + ++wi; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":palette" and + * indicates the appropriate size for the palette images. + */ +bool TxaFile:: +parse_palette_line(const vector_string &words) { + if (words.size() != 3) { + nout << "Exactly two parameters required for :palette, the x and y " + << "size of the palette images to generate.\n"; + return false; + } + + if (!string_to_int(words[1], pal->_pal_x_size) || + !string_to_int(words[2], pal->_pal_y_size)) { + nout << "Invalid palette size: " << words[1] << " " << words[2] << "\n"; + return false; + } + + if (pal->_pal_x_size <= 0 || pal->_pal_y_size <= 0) { + nout << "Invalid palette size: " << pal->_pal_x_size + << " " << pal->_pal_y_size << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":margin" and + * indicates the default margin size. + */ +bool TxaFile:: +parse_margin_line(const vector_string &words) { + if (words.size() != 2) { + nout << "Exactly one parameter required for :margin, the " + << "size of the default margin to apply.\n"; + return false; + } + + if (!string_to_int(words[1], pal->_margin)) { + nout << "Invalid margin: " << words[1] << "\n"; + return false; + } + + if (pal->_margin < 0) { + nout << "Invalid margin: " << pal->_margin << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":background" + * and indicates the palette background color. + */ +bool TxaFile:: +parse_background_line(const vector_string &words) { + if (words.size() != 5) { + nout << "Exactly four parameter required for :background: the " + << "four [r g b a] components of the background color.\n"; + return false; + } + + if (!string_to_double(words[1], pal->_background[0]) || + !string_to_double(words[2], pal->_background[1]) || + !string_to_double(words[3], pal->_background[2]) || + !string_to_double(words[4], pal->_background[3])) { + nout << "Invalid color: " + << words[1] << " " << words[2] << " " + << words[3] << " " << words[4] << " " << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":coverage" + * and indicates the default coverage threshold. + */ +bool TxaFile:: +parse_coverage_line(const vector_string &words) { + if (words.size() != 2) { + nout << "Exactly one parameter required for :coverage, the " + << "value for the default coverage threshold.\n"; + return false; + } + + + if (!string_to_double(words[1], pal->_coverage_threshold)) { + nout << "Invalid coverage threshold: " << words[1] << "\n"; + return false; + } + + if (pal->_coverage_threshold <= 0.0) { + nout << "Invalid coverage threshold: " << pal->_coverage_threshold << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":powertwo" + * and indicates whether textures should by default be forced to a power of + * two. + */ +bool TxaFile:: +parse_powertwo_line(const vector_string &words) { + if (words.size() != 2) { + nout << "Exactly one parameter required for :powertwo, either a 0 " + << "or a 1.\n"; + return false; + } + + int flag; + if (!string_to_int(words[1], flag)) { + nout << "Invalid powertwo flag: " << words[1] << "\n"; + return false; + } + + if (flag != 0 && flag != 1) { + nout << "Invalid powertwo flag: " << flag << "\n"; + return false; + } + + pal->_force_power_2 = (flag != 0); + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":imagetype" + * and indicates the default image file type to convert palettes and textures + * to. + */ +bool TxaFile:: +parse_imagetype_line(const vector_string &words) { + if (words.size() != 2) { + nout << "Exactly one parameter required for :imagetype.\n"; + return false; + } + const string &imagetype = words[1]; + if (!parse_image_type_request(imagetype, pal->_color_type, pal->_alpha_type)) { + nout << "\nKnown image types are:\n"; + PNMFileTypeRegistry::get_global_ptr()->write(nout, 2); + nout << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":shadowtype" + * and indicates the image file type to convert working copies of the palette + * images to. + */ +bool TxaFile:: +parse_shadowtype_line(const vector_string &words) { + if (words.size() != 2) { + nout << "Exactly one parameter required for :shadowtype.\n"; + return false; + } + const string &shadowtype = words[1]; + if (!parse_image_type_request(shadowtype, pal->_shadow_color_type, + pal->_shadow_alpha_type)) { + nout << "\nKnown image types are:\n"; + PNMFileTypeRegistry::get_global_ptr()->write(nout, 2); + nout << "\n"; + return false; + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":round" and + * indicates how or whether to round up UV minmax boxes. + */ +bool TxaFile:: +parse_round_line(const vector_string &words) { + if (words.size() == 2) { + if (words[1] == "no") { + pal->_round_uvs = false; + return true; + } else { + nout << "Invalid round keyword: " << words[1] << ".\n" + << "Expected 'no', or a round fraction and fuzz factor.\n"; + return false; + } + } + + if (words.size() != 3) { + nout << "Exactly two parameters required for :round, the fraction " + << "to round to, and the fuzz factor.\n"; + return false; + } + + if (!string_to_double(words[1], pal->_round_unit) || + !string_to_double(words[2], pal->_round_fuzz)) { + nout << "Invalid rounding: " << words[1] << " " << words[2] << "\n"; + return false; + } + + if (pal->_round_unit <= 0.0 || pal->_round_fuzz < 0.0) { + nout << "Invalid rounding: " << pal->_round_unit + << " " << pal->_round_fuzz << "\n"; + return false; + } + + pal->_round_uvs = true; + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":remap" and + * indicates how or whether to remap UV coordinates in egg files to the unit + * box. + */ +bool TxaFile:: +parse_remap_line(const vector_string &words) { + int i = 1; + while (i < (int)words.size()) { + const string &keyword = words[i]; + if (keyword == "char") { + // Defining how to remap UV's for characters. + i++; + if (i == (int)words.size()) { + nout << "Keyword expected following 'char'\n"; + return false; + } + pal->_remap_char_uv = Palettizer::string_remap(words[i]); + if (pal->_remap_char_uv == Palettizer::RU_invalid) { + nout << "Invalid remap keyword: " << words[i] << "\n"; + return false; + } + + } else { + // Defining how to remap UV's in general. + pal->_remap_uv = Palettizer::string_remap(words[i]); + if (pal->_remap_uv == Palettizer::RU_invalid) { + nout << "Invalid remap keyword: " << words[i] << "\n"; + return false; + } + + pal->_remap_char_uv = pal->_remap_uv; + } + + i++; + } + + return true; +} + + +/** + * Handles the line in a .txa file that begins with the keyword ":cutout" and + * indicates how to handle alpha-cutout textures: those textures that appear + * to be mostly solid parts and invisible parts, with a thin border of + * antialiased alpha along the boundary. + */ +bool TxaFile:: +parse_cutout_line(const vector_string &words) { + if (words.size() < 2 || words.size() > 3) { + nout << ":cutout alpha-mode [ratio]\n"; + return false; + } + + EggRenderMode::AlphaMode am = EggRenderMode::string_alpha_mode(words[1]); + if (am == EggRenderMode::AM_unspecified) { + nout << "Invalid cutout keyword: " << words[1] << "\n"; + return false; + } + pal->_cutout_mode = am; + + if (words.size() >= 3) { + if (!string_to_double(words[2], pal->_cutout_ratio)) { + nout << "Invalid cutout ratio: " << words[2] << "\n"; + } + } + + return true; +} + +/** + * Handles the line in a .txa file that begins with the keyword ":textureswap" + * and indicates the relationships between textures to be swapped. + */ +bool TxaFile:: +parse_textureswap_line(const vector_string &words) { + vector_string::const_iterator wi; + wi = words.begin(); + assert (wi != words.end()); + ++wi; + + const string &group_name = (*wi); + PaletteGroup *group = pal->get_palette_group(group_name); + ++wi; + + string sourceTextureName = (*wi); + ++wi; + + // vector_string swapTextures; copy(words.begin(), words.end(), + // swapTextures); group->add_texture_swap_info(sourceTextureName, + // swapTextures); + size_t dot = sourceTextureName.rfind('.'); + if (dot != string::npos) { + sourceTextureName = sourceTextureName.substr(0, dot); + } + group->add_texture_swap_info(sourceTextureName, words); + + return true; +} diff --git a/pandatool/src/palettizer/txaFile.h b/pandatool/src/palettizer/txaFile.h new file mode 100644 index 00000000..05d0a32a --- /dev/null +++ b/pandatool/src/palettizer/txaFile.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaFile.h + * @author drose + * @date 2000-11-30 + */ + +#ifndef TXAFILE_H +#define TXAFILE_H + +#include "pandatoolbase.h" + +#include "txaLine.h" + +#include "filename.h" +#include "vector_string.h" + +#include "pvector.h" + +/** + * This represents the .txa file (usually textures.txa) that contains the user + * instructions for resizing, grouping, etc. the various textures. + */ +class TxaFile { +public: + TxaFile(); + + bool read(std::istream &in, const std::string &filename); + + bool match_egg(EggFile *egg_file) const; + bool match_texture(TextureImage *texture) const; + + void write(std::ostream &out) const; + +private: + static int get_line_or_semicolon(std::istream &in, std::string &line); + + bool parse_group_line(const vector_string &words); + bool parse_palette_line(const vector_string &words); + bool parse_margin_line(const vector_string &words); + bool parse_background_line(const vector_string &words); + bool parse_coverage_line(const vector_string &words); + bool parse_powertwo_line(const vector_string &words); + bool parse_imagetype_line(const vector_string &words); + bool parse_shadowtype_line(const vector_string &words); + bool parse_round_line(const vector_string &words); + bool parse_remap_line(const vector_string &words); + bool parse_cutout_line(const vector_string &words); + bool parse_textureswap_line(const vector_string &words); + + typedef pvector Lines; + Lines _lines; +}; + +#endif diff --git a/pandatool/src/palettizer/txaLine.cxx b/pandatool/src/palettizer/txaLine.cxx new file mode 100644 index 00000000..bf3b645c --- /dev/null +++ b/pandatool/src/palettizer/txaLine.cxx @@ -0,0 +1,621 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaLine.cxx + * @author drose + * @date 2000-11-30 + */ + +#include "txaLine.h" +#include "pal_string_utils.h" +#include "eggFile.h" +#include "palettizer.h" +#include "textureImage.h" +#include "sourceTextureImage.h" +#include "paletteGroup.h" + +#include "pnotify.h" +#include "pnmFileType.h" + +using std::string; + +/** + * + */ +TxaLine:: +TxaLine() { + _size_type = ST_none; + _scale = 0.0; + _x_size = 0; + _y_size = 0; + _aniso_degree = 0; + _num_channels = 0; + _format = EggTexture::F_unspecified; + _force_format = false; + _generic_format = false; + _keep_format = false; + _alpha_mode = EggRenderMode::AM_unspecified; + _wrap_u = EggTexture::WM_unspecified; + _wrap_v = EggTexture::WM_unspecified; + _quality_level = EggTexture::QL_unspecified; + _got_margin = false; + _margin = 0; + _got_coverage_threshold = false; + _coverage_threshold = 0.0; + _color_type = nullptr; + _alpha_type = nullptr; +} + +/** + * Accepts a string that defines a line of the .txa file and parses it into + * its constinuent parts. Returns true if successful, false on error. + */ +bool TxaLine:: +parse(const string &line) { + size_t colon = line.find(':'); + if (colon == string::npos) { + nout << "Colon required.\n"; + return false; + } + + // Chop up the first part of the string (preceding the colon) into its + // individual words. These are patterns to match. + vector_string words; + extract_words(line.substr(0, colon), words); + + vector_string::iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + string word = (*wi); + + // If the pattern ends in the string ".egg", and only if it ends in this + // string, it is deemed an egg pattern and will only be tested against egg + // files. If it ends in anything else, it is deemed a texture pattern and + // will only be tested against textures. + if (word.length() > 4 && word.substr(word.length() - 4) == ".egg") { + GlobPattern pattern(word); + pattern.set_case_sensitive(false); + _egg_patterns.push_back(pattern); + + } else { + // However, the filename extension, if any, is stripped off because the + // texture key names nowadays don't include them. + size_t dot = word.rfind('.'); + if (dot != string::npos) { + word = word.substr(0, dot); + } + GlobPattern pattern(word); + pattern.set_case_sensitive(false); + _texture_patterns.push_back(pattern); + } + } + + if (_egg_patterns.empty() && _texture_patterns.empty()) { + nout << "No texture or egg filenames given.\n"; + return false; + } + + // Now chop up the rest of the string (following the colon) into its + // individual words. These are keywords and size indications. + words.clear(); + extract_words(line.substr(colon + 1), words); + + wi = words.begin(); + while (wi != words.end()) { + const string &word = *wi; + nassertr(!word.empty(), false); + + if (isdigit(word[0])) { + // This is either a new size or a scale percentage. + if (_size_type != ST_none) { + nout << "Invalid repeated size request: " << word << "\n"; + return false; + } + if (word[word.length() - 1] == '%') { + // It's a scale percentage! + _size_type = ST_scale; + + string tail; + _scale = string_to_double(word, tail); + if (!(tail == "%")) { + // This is an invalid number. + return false; + } + ++wi; + + } else { + // Collect a number of consecutive numeric fields. + pvector numbers; + while (wi != words.end() && isdigit((*wi)[0])) { + const string &word = *wi; + int num; + if (!string_to_int(word, num)) { + nout << "Invalid size: " << word << "\n"; + return false; + } + numbers.push_back(num); + ++wi; + } + if (numbers.size() < 2) { + nout << "At least two size numbers must be given, or a percent sign used to indicate scaling.\n"; + return false; + + } else if (numbers.size() == 2) { + _size_type = ST_explicit_2; + _x_size = numbers[0]; + _y_size = numbers[1]; + + } else if (numbers.size() == 3) { + _size_type = ST_explicit_3; + _x_size = numbers[0]; + _y_size = numbers[1]; + _num_channels = numbers[2]; + + } else { + nout << "Too many size numbers given.\n"; + return false; + } + } + + } else { + // The word does not begin with a digit; therefore it's either a keyword + // or an image file type request. + if (word == "omit") { + _keywords.push_back(KW_omit); + + } else if (word == "nearest") { + _keywords.push_back(KW_nearest); + + } else if (word == "linear") { + _keywords.push_back(KW_linear); + + } else if (word == "mipmap") { + _keywords.push_back(KW_mipmap); + + } else if (word == "cont") { + _keywords.push_back(KW_cont); + + } else if (word == "srgb") { + _keywords.push_back(KW_srgb); + + } else if (word == "margin") { + ++wi; + if (wi == words.end()) { + nout << "Argument required for 'margin'.\n"; + return false; + } + + const string &arg = (*wi); + if (!string_to_int(arg, _margin)) { + nout << "Not an integer: " << arg << "\n"; + return false; + } + if (_margin < 0) { + nout << "Invalid margin: " << _margin << "\n"; + return false; + } + _got_margin = true; + + } else if (word == "aniso") { + ++wi; + if (wi == words.end()) { + nout << "Integer argument required for 'aniso'.\n"; + return false; + } + + const string &arg = (*wi); + if (!string_to_int(arg, _aniso_degree)) { + nout << "Not an integer: " << arg << "\n"; + return false; + } + if ((_aniso_degree < 2) || (_aniso_degree > 16)) { + // make it an error to specific degree 0 or 1, which means no + // anisotropy so it's probably an input mistake + nout << "Invalid anistropic degree (range is 2-16): " << _aniso_degree << "\n"; + return false; + } + + _keywords.push_back(KW_anisotropic); + + } else if (word == "coverage") { + ++wi; + if (wi == words.end()) { + nout << "Argument required for 'coverage'.\n"; + return false; + } + + const string &arg = (*wi); + if (!string_to_double(arg, _coverage_threshold)) { + nout << "Not a number: " << arg << "\n"; + return false; + } + if (_coverage_threshold <= 0.0) { + nout << "Invalid coverage threshold: " << _coverage_threshold << "\n"; + return false; + } + _got_coverage_threshold = true; + + } else if (word.substr(0, 6) == "force-") { + // Force a particular format, despite the number of channels in the + // image. + string format_name = word.substr(6); + EggTexture::Format format = EggTexture::string_format(format_name); + if (format != EggTexture::F_unspecified) { + _format = format; + _force_format = true; + } else { + nout << "Unknown image format: " << format_name << "\n"; + return false; + } + + } else if (word == "generic") { + // Genericize the image format by replacing bitcount-specific formats + // with their generic equivalents, e.g. rgba8 becomes rgba. + _generic_format = true; + + } else if (word == "keep-format") { + // Keep whatever image format was specified. + _keep_format = true; + + } else { + // Maybe it's a group name. + PaletteGroup *group = pal->test_palette_group(word); + if (group != nullptr) { + _palette_groups.insert(group); + + } else { + // Maybe it's a format name. This suggests an image format, but may + // be overridden to reflect the number of channels in the image. + EggTexture::Format format = EggTexture::string_format(word); + if (format != EggTexture::F_unspecified) { + if (!_force_format) { + _format = format; + } + } else { + // Maybe it's an alpha mode. + EggRenderMode::AlphaMode am = EggRenderMode::string_alpha_mode(word); + if (am != EggRenderMode::AM_unspecified) { + _alpha_mode = am; + + } else { + // Maybe it's a quality level. + EggTexture::QualityLevel ql = EggTexture::string_quality_level(word); + if (ql != EggTexture::QL_unspecified) { + _quality_level = ql; + + } else if (word.length() > 2 && word[word.length() - 2] == '_' && + strchr("uv", word[word.length() - 1]) != nullptr) { + // It must be a wrap mode for u or v. + string prefix = word.substr(0, word.length() - 2); + EggTexture::WrapMode wm = EggTexture::string_wrap_mode(prefix); + if (wm == EggTexture::WM_unspecified) { + return false; + } + switch (word[word.length() - 1]) { + case 'u': + _wrap_u = wm; + break; + + case 'v': + _wrap_v = wm; + break; + } + + } else { + // Maybe it's an image file request. + if (!parse_image_type_request(word, _color_type, _alpha_type)) { + return false; + } + } + } + } + } + } + ++wi; + } + } + + return true; +} + +/** + * Compares the patterns on the line to the indicated EggFile. If they match, + * updates the egg with the appropriate information. Returns true if a match + * is detected and the search for another line should stop, or false if a + * match is not detected (or if the keyword "cont" is present, which means the + * search should continue regardless). + */ +bool TxaLine:: +match_egg(EggFile *egg_file) const { + string name = egg_file->get_name(); + + bool matched_any = false; + Patterns::const_iterator pi; + for (pi = _egg_patterns.begin(); + pi != _egg_patterns.end() && !matched_any; + ++pi) { + matched_any = (*pi).matches(name); + } + + if (!matched_any) { + // No match this line; continue. + return false; + } + + bool got_cont = false; + Keywords::const_iterator ki; + for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) { + switch (*ki) { + case KW_omit: + break; + + case KW_nearest: + case KW_linear: + case KW_mipmap: + case KW_anisotropic: + case KW_srgb: + // These mean nothing to an egg file. + break; + + case KW_cont: + got_cont = true; + break; + } + } + + egg_file->match_txa_groups(_palette_groups); + + if (got_cont) { + // If we have the "cont" keyword, we should keep scanning for another + // line, even though we matched this one. + return false; + } + + // Otherwise, in the normal case, a match ends the search for matches. + egg_file->clear_surprise(); + + return true; +} + +/** + * Compares the patterns on the line to the indicated TextureImage. If they + * match, updates the texture with the appropriate information. Returns true + * if a match is detected and the search for another line should stop, or + * false if a match is not detected (or if the keyword "cont" is present, + * which means the search should continue regardless). + */ +bool TxaLine:: +match_texture(TextureImage *texture) const { + string name = texture->get_name(); + + bool matched_any = false; + Patterns::const_iterator pi; + for (pi = _texture_patterns.begin(); + pi != _texture_patterns.end() && !matched_any; + ++pi) { + matched_any = (*pi).matches(name); + } + + if (!matched_any) { + // No match this line; continue. + return false; + } + + SourceTextureImage *source = texture->get_preferred_source(); + TextureRequest &request = texture->_request; + + if (!request._got_size) { + switch (_size_type) { + case ST_none: + break; + + case ST_scale: + if (source != nullptr && source->get_size()) { + request._got_size = true; + request._x_size = std::max(1, (int)(source->get_x_size() * _scale / 100.0)); + request._y_size = std::max(1, (int)(source->get_y_size() * _scale / 100.0)); + } + break; + + case ST_explicit_3: + request._got_num_channels = true; + request._num_channels = _num_channels; + // fall through + + case ST_explicit_2: + request._got_size = true; + request._x_size = _x_size; + request._y_size = _y_size; + break; + } + } + + if (_got_margin) { + request._margin = _margin; + } + + if (_got_coverage_threshold) { + request._coverage_threshold = _coverage_threshold; + } + + if (_color_type != nullptr) { + request._properties._color_type = _color_type; + request._properties._alpha_type = _alpha_type; + } + + if (_quality_level != EggTexture::QL_unspecified) { + request._properties._quality_level = _quality_level; + } + + if (_format != EggTexture::F_unspecified) { + request._format = _format; + request._force_format = _force_format; + request._generic_format = false; + } + + if (_generic_format) { + request._generic_format = true; + } + + if (_keep_format) { + request._keep_format = true; + } + + if (_alpha_mode != EggRenderMode::AM_unspecified) { + request._alpha_mode = _alpha_mode; + } + + if (_wrap_u != EggTexture::WM_unspecified) { + request._wrap_u = _wrap_u; + } + if (_wrap_v != EggTexture::WM_unspecified) { + request._wrap_v = _wrap_v; + } + + bool got_cont = false; + Keywords::const_iterator ki; + for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) { + switch (*ki) { + case KW_omit: + request._omit = true; + break; + + case KW_nearest: + request._minfilter = EggTexture::FT_nearest; + request._magfilter = EggTexture::FT_nearest; + break; + + case KW_linear: + request._minfilter = EggTexture::FT_linear; + request._magfilter = EggTexture::FT_linear; + break; + + case KW_mipmap: + request._minfilter = EggTexture::FT_linear_mipmap_linear; + request._magfilter = EggTexture::FT_linear; + break; + + case KW_anisotropic: + request._anisotropic_degree = _aniso_degree; + break; + + case KW_cont: + got_cont = true; + break; + + case KW_srgb: + request._srgb = true; + } + } + + texture->_explicitly_assigned_groups.make_union + (texture->_explicitly_assigned_groups, _palette_groups); + texture->_explicitly_assigned_groups.remove_null(); + + if (got_cont) { + // If we have the "cont" keyword, we should keep scanning for another + // line, even though we matched this one. + return false; + } + + // Otherwise, in the normal case, a match ends the search for matches. + texture->_is_surprise = false; + + return true; +} + +/** + * + */ +void TxaLine:: +output(std::ostream &out) const { + Patterns::const_iterator pi; + for (pi = _texture_patterns.begin(); pi != _texture_patterns.end(); ++pi) { + out << (*pi) << " "; + } + for (pi = _egg_patterns.begin(); pi != _egg_patterns.end(); ++pi) { + out << (*pi) << " "; + } + out << ":"; + + switch (_size_type) { + case ST_none: + break; + + case ST_scale: + out << " " << _scale << "%"; + break; + + case ST_explicit_2: + out << " " << _x_size << " " << _y_size; + break; + + case ST_explicit_3: + out << " " << _x_size << " " << _y_size << " " << _num_channels; + break; + } + + if (_got_margin) { + out << " margin " << _margin; + } + + if (_got_coverage_threshold) { + out << " coverage " << _coverage_threshold; + } + + Keywords::const_iterator ki; + for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) { + switch (*ki) { + case KW_omit: + out << " omit"; + break; + + case KW_nearest: + out << " nearest"; + break; + + case KW_linear: + out << " linear"; + break; + + case KW_mipmap: + out << " mipmap"; + break; + + case KW_cont: + out << " cont"; + break; + + case KW_anisotropic: + out << " aniso " << _aniso_degree; + break; + + case KW_srgb: + out << " srgb"; + break; + } + } + + PaletteGroups::const_iterator gi; + for (gi = _palette_groups.begin(); gi != _palette_groups.end(); ++gi) { + out << " " << (*gi)->get_name(); + } + + if (_format != EggTexture::F_unspecified) { + out << " " << _format; + if (_force_format) { + out << " (forced)"; + } + } + + if (_color_type != nullptr) { + out << " " << _color_type->get_suggested_extension(); + if (_alpha_type != nullptr) { + out << "," << _alpha_type->get_suggested_extension(); + } + } +} diff --git a/pandatool/src/palettizer/txaLine.h b/pandatool/src/palettizer/txaLine.h new file mode 100644 index 00000000..8a93c45a --- /dev/null +++ b/pandatool/src/palettizer/txaLine.h @@ -0,0 +1,102 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file txaLine.h + * @author drose + * @date 2000-11-30 + */ + +#ifndef TXALINE_H +#define TXALINE_H + +#include "pandatoolbase.h" + +#include "paletteGroups.h" + +#include "globPattern.h" +#include "eggTexture.h" +#include "eggRenderMode.h" + +#include "pvector.h" + +class PNMFileType; +class EggFile; +class TextureImage; + +/** + * This is a single matching line in the .txa file. It consists of a list of + * names (texture names or egg file names), followed by a colon and an + * optional size and a set of keywords. + */ +class TxaLine { +public: + TxaLine(); + + bool parse(const std::string &line); + + bool match_egg(EggFile *egg_file) const; + bool match_texture(TextureImage *texture) const; + + void output(std::ostream &out) const; + +private: + typedef pvector Patterns; + Patterns _texture_patterns; + Patterns _egg_patterns; + + enum SizeType { + ST_none, + ST_scale, + ST_explicit_2, + ST_explicit_3 + }; + + SizeType _size_type; + PN_stdfloat _scale; + int _x_size; + int _y_size; + int _num_channels; + EggTexture::Format _format; + bool _force_format; + bool _generic_format; + bool _keep_format; + EggRenderMode::AlphaMode _alpha_mode; + EggTexture::WrapMode _wrap_u, _wrap_v; + EggTexture::QualityLevel _quality_level; + + int _aniso_degree; + bool _got_margin; + int _margin; + bool _got_coverage_threshold; + double _coverage_threshold; + + enum Keyword { + KW_omit, + KW_nearest, + KW_linear, + KW_mipmap, + KW_cont, + KW_anisotropic, + KW_srgb, + }; + + typedef pvector Keywords; + Keywords _keywords; + + PaletteGroups _palette_groups; + + PNMFileType *_color_type; + PNMFileType *_alpha_type; +}; + +INLINE std::ostream &operator << (std::ostream &out, const TxaLine &line) { + line.output(out); + return out; +} + +#endif diff --git a/pandatool/src/pandatoolbase/CMakeLists.txt b/pandatool/src/pandatoolbase/CMakeLists.txt new file mode 100644 index 00000000..6c8165d5 --- /dev/null +++ b/pandatool/src/pandatoolbase/CMakeLists.txt @@ -0,0 +1,36 @@ +set(P3PANDATOOLBASE_HEADERS + animationConvert.h + config_pandatoolbase.h + distanceUnit.h + pandatoolbase.h pandatoolsymbols.h + pathReplace.h pathReplace.I + pathStore.h +) + +set(P3PANDATOOLBASE_SOURCES + animationConvert.cxx + config_pandatoolbase.cxx + distanceUnit.cxx + pandatoolbase.cxx + pathReplace.cxx + pathStore.cxx +) + +composite_sources(p3pandatoolbase P3PANDATOOLBASE_SOURCES) +add_library(p3pandatoolbase STATIC ${P3PANDATOOLBASE_HEADERS} ${P3PANDATOOLBASE_SOURCES}) +target_link_libraries(p3pandatoolbase panda) + +# ptloader - a module - may need to link in libraries in this package, so: +if(MODULE_TYPE STREQUAL "MODULE") + set_target_properties(p3pandatoolbase PROPERTIES + POSITION_INDEPENDENT_CODE ON + INTERFACE_POSITION_INDEPENDENT_CODE ON) +endif() + +install(TARGETS p3pandatoolbase + EXPORT ToolsDevel COMPONENT ToolsDevel + DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d + ARCHIVE COMPONENT ToolsDevel) +install(FILES ${P3PANDATOOLBASE_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) diff --git a/pandatool/src/pandatoolbase/animationConvert.cxx b/pandatool/src/pandatoolbase/animationConvert.cxx new file mode 100644 index 00000000..d09ce0f1 --- /dev/null +++ b/pandatool/src/pandatoolbase/animationConvert.cxx @@ -0,0 +1,91 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file animationConvert.cxx + * @author drose + * @date 2003-01-21 + */ + +#include "animationConvert.h" + +#include "string_utils.h" +#include "pnotify.h" + +/** + * Returns the string corresponding to this method. + */ +std::string +format_animation_convert(AnimationConvert convert) { + switch (convert) { + case AC_invalid: + return "invalid"; + + case AC_none: + return "none"; + + case AC_pose: + return "pose"; + + case AC_flip: + return "flip"; + + case AC_strobe: + return "strobe"; + + case AC_model: + return "model"; + + case AC_chan: + return "chan"; + + case AC_both: + return "both"; + } + nout << "**unexpected AnimationConvert value: (" << (int)convert << ")**"; + return "**"; +} + +/** + * + */ +std::ostream & +operator << (std::ostream &out, AnimationConvert convert) { + return out << format_animation_convert(convert); +} + +/** + * Converts from a string, as might be input by the user, to one of the known + * AnimationConvert types. Returns AC_invalid if the string is unknown. + */ +AnimationConvert +string_animation_convert(const std::string &str) { + if (cmp_nocase(str, "none") == 0) { + return AC_none; + + } else if (cmp_nocase(str, "pose") == 0) { + return AC_pose; + + } else if (cmp_nocase(str, "flip") == 0) { + return AC_flip; + + } else if (cmp_nocase(str, "strobe") == 0) { + return AC_strobe; + + } else if (cmp_nocase(str, "model") == 0) { + return AC_model; + + } else if (cmp_nocase(str, "chan") == 0) { + return AC_chan; + + } else if (cmp_nocase(str, "both") == 0) { + return AC_both; + + } else { + return AC_invalid; + } +} diff --git a/pandatool/src/pandatoolbase/animationConvert.h b/pandatool/src/pandatoolbase/animationConvert.h new file mode 100644 index 00000000..2365cfff --- /dev/null +++ b/pandatool/src/pandatoolbase/animationConvert.h @@ -0,0 +1,39 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file animationConvert.h + * @author drose + * @date 2003-01-21 + */ + +#ifndef ANIMATIONCONVERT_H +#define ANIMATIONCONVERT_H + +#include "pandatoolbase.h" + +/** + * This enumerated type lists the methods by which animation from an animation + * package might be represented in egg format. + */ +enum AnimationConvert { + AC_invalid, // Never use this. + AC_none, // No animation: static geometry only. + AC_pose, // Pose to one frame, then get static geometry. + AC_flip, // A flip (sequence) of static geometry models. + AC_strobe, // All frames of a flip visible at the same time. + AC_model, // A character model, with joints. + AC_chan, // Animation tables for the above model. + AC_both, // A character model and tables in the same file. +}; + +std::string format_animation_convert(AnimationConvert unit); + +std::ostream &operator << (std::ostream &out, AnimationConvert unit); +AnimationConvert string_animation_convert(const std::string &str); + +#endif diff --git a/pandatool/src/pandatoolbase/config_pandatoolbase.cxx b/pandatool/src/pandatoolbase/config_pandatoolbase.cxx new file mode 100644 index 00000000..65f02228 --- /dev/null +++ b/pandatool/src/pandatoolbase/config_pandatoolbase.cxx @@ -0,0 +1,31 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_pandatoolbase.cxx + * @author drose + * @date 2004-11-29 + */ + +#include "config_pandatoolbase.h" + +NotifyCategoryDef(pandatoolbase, ""); + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libpandatoolbase() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; +} diff --git a/pandatool/src/pandatoolbase/config_pandatoolbase.h b/pandatool/src/pandatoolbase/config_pandatoolbase.h new file mode 100644 index 00000000..2bfe9c9f --- /dev/null +++ b/pandatool/src/pandatoolbase/config_pandatoolbase.h @@ -0,0 +1,25 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_pandatoolbase.h + * @author drose + * @date 2004-11-29 + */ + +#ifndef CONFIG_PANDATOOLBASE_H +#define CONFIG_PANDATOOLBASE_H + +#include "pandatoolbase.h" + +#include "notifyCategoryProxy.h" + +NotifyCategoryDeclNoExport(pandatoolbase); + +extern void init_libpandatoolbase(); + +#endif diff --git a/pandatool/src/pandatoolbase/distanceUnit.cxx b/pandatool/src/pandatoolbase/distanceUnit.cxx new file mode 100644 index 00000000..8ee495be --- /dev/null +++ b/pandatool/src/pandatoolbase/distanceUnit.cxx @@ -0,0 +1,215 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file distanceUnit.cxx + * @author drose + * @date 2001-04-17 + */ + +#include "distanceUnit.h" +#include "config_pandatoolbase.h" +#include "string_utils.h" +#include "pnotify.h" + +using std::istream; +using std::ostream; +using std::string; + +/** + * Returns the string representing the common abbreviation for the given unit. + */ +string +format_abbrev_unit(DistanceUnit unit) { + switch (unit) { + case DU_millimeters: + return "mm"; + + case DU_centimeters: + return "cm"; + + case DU_meters: + return "m"; + + case DU_kilometers: + return "km"; + + case DU_yards: + return "yd"; + + case DU_feet: + return "ft"; + + case DU_inches: + return "in"; + + case DU_nautical_miles: + return "nmi"; + + case DU_statute_miles: + return "mi"; + + case DU_invalid: + return "invalid"; + } + nout << "**unexpected DistanceUnit value: (" << (int)unit << ")**"; + return "**"; +} + +/** + * Returns the string representing the full name (plural) for the given unit. + */ +string +format_long_unit(DistanceUnit unit) { + switch (unit) { + case DU_millimeters: + return "millimeters"; + + case DU_centimeters: + return "centimeters"; + + case DU_meters: + return "meters"; + + case DU_kilometers: + return "kilometers"; + + case DU_yards: + return "yards"; + + case DU_feet: + return "feet"; + + case DU_inches: + return "inches"; + + case DU_nautical_miles: + return "nautical miles"; + + case DU_statute_miles: + return "miles"; + + case DU_invalid: + return "invalid"; + } + nout << "**unexpected DistanceUnit value: (" << (int)unit << ")**"; + return "**"; +} + +/** + * + */ +ostream & +operator << (ostream &out, DistanceUnit unit) { + return out << format_abbrev_unit(unit); +} + +/** + * + */ +istream & +operator >> (istream &in, DistanceUnit &unit) { + string word; + in >> word; + unit = string_distance_unit(word); + if (unit == DU_invalid) { + pandatoolbase_cat->error() + << "Invalid distance unit: " << word << "\n"; + } + return in; +} + +/** + * Converts from a string, as might be input by the user, to one of the known + * DistanceUnit types. Returns DU_invalid if the string is unknown. + */ +DistanceUnit +string_distance_unit(const string &str) { + if (cmp_nocase(str, "mm") == 0 || cmp_nocase(str, "millimeters") == 0) { + return DU_millimeters; + + } else if (cmp_nocase(str, "cm") == 0 || cmp_nocase(str, "centimeters") == 0) { + return DU_centimeters; + + } else if (cmp_nocase(str, "m") == 0 || cmp_nocase(str, "meters") == 0) { + return DU_meters; + + } else if (cmp_nocase(str, "km") == 0 || cmp_nocase(str, "kilometers") == 0) { + return DU_kilometers; + + } else if (cmp_nocase(str, "yd") == 0 || cmp_nocase(str, "yards") == 0) { + return DU_yards; + + } else if (cmp_nocase(str, "ft") == 0 || cmp_nocase(str, "feet") == 0) { + return DU_feet; + + } else if (cmp_nocase(str, "in") == 0 || cmp_nocase(str, "inches") == 0) { + return DU_inches; + + } else if (cmp_nocase(str, "nmi") == 0 || + cmp_nocase(str, "nm") == 0 || + cmp_nocase_uh(str, "nautical_miles") == 0) { + return DU_nautical_miles; + + } else if (cmp_nocase(str, "mi") == 0 || + cmp_nocase(str, "miles") == 0 || + cmp_nocase_uh(str, "statute_miles") == 0) { + return DU_statute_miles; + + } else { + return DU_invalid; + } +} + +/** + * Returns the number of the indicated unit per each centimeter. This + * internal function is used to implement convert_units(), below. + */ +static double unit_scale(DistanceUnit unit) { + switch (unit) { + case DU_millimeters: + return 0.1; + + case DU_centimeters: + return 1.0; + + case DU_meters: + return 100.0; + + case DU_kilometers: + return 100000.0; + + case DU_yards: + return 3.0 * 12.0 * 2.54; + + case DU_feet: + return 12.0 * 2.54; + + case DU_inches: + return 2.54; + + case DU_nautical_miles: + // This is the U.S. definition. + return 185200.0; + + case DU_statute_miles: + return 5280.0 * 12.0 * 2.54; + + case DU_invalid: + return 1.0; + } + + return 1.0; +} + +/** + * Returns the scaling factor that must be applied to convert from units of + * "from" to "to". + */ +double convert_units(DistanceUnit from, DistanceUnit to) { + return unit_scale(from) / unit_scale(to); +} diff --git a/pandatool/src/pandatoolbase/distanceUnit.h b/pandatool/src/pandatoolbase/distanceUnit.h new file mode 100644 index 00000000..2030ae09 --- /dev/null +++ b/pandatool/src/pandatoolbase/distanceUnit.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file distanceUnit.h + * @author drose + * @date 2001-04-17 + */ + +#ifndef DISTANCEUNIT_H +#define DISTANCEUNIT_H + +#include "pandatoolbase.h" + +/** + * This enumerated type lists all the kinds of units we're likely to come + * across in model conversion programs. + */ +enum DistanceUnit { + DU_millimeters, + DU_centimeters, + DU_meters, + DU_kilometers, + DU_yards, + DU_feet, + DU_inches, + DU_nautical_miles, + DU_statute_miles, + DU_invalid +}; + +std::string format_abbrev_unit(DistanceUnit unit); +std::string format_long_unit(DistanceUnit unit); + +std::ostream &operator << (std::ostream &out, DistanceUnit unit); +std::istream &operator >> (std::istream &in, DistanceUnit &unit); +DistanceUnit string_distance_unit(const std::string &str); + +double convert_units(DistanceUnit from, DistanceUnit to); + +#endif diff --git a/pandatool/src/pandatoolbase/p3pandatoolbase_composite1.cxx b/pandatool/src/pandatoolbase/p3pandatoolbase_composite1.cxx new file mode 100644 index 00000000..56ce2f5e --- /dev/null +++ b/pandatool/src/pandatoolbase/p3pandatoolbase_composite1.cxx @@ -0,0 +1,6 @@ +#include "config_pandatoolbase.cxx" +#include "pathStore.cxx" +#include "pathReplace.cxx" +#include "animationConvert.cxx" +#include "distanceUnit.cxx" +#include "pandatoolbase.cxx" diff --git a/pandatool/src/pandatoolbase/pandatoolbase.cxx b/pandatool/src/pandatoolbase/pandatoolbase.cxx new file mode 100644 index 00000000..834ec05b --- /dev/null +++ b/pandatool/src/pandatoolbase/pandatoolbase.cxx @@ -0,0 +1,14 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandatoolbase.cxx + * @author drose + * @date 2000-09-15 + */ + +#include "pandatoolbase.h" diff --git a/pandatool/src/pandatoolbase/pandatoolbase.h b/pandatool/src/pandatoolbase/pandatoolbase.h new file mode 100644 index 00000000..2d87f29b --- /dev/null +++ b/pandatool/src/pandatoolbase/pandatoolbase.h @@ -0,0 +1,24 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandatoolbase.h + * @author drose + * @date 2000-09-12 + */ + +/* This file is included at the beginning of every header file and/or + C or C++ file. It must be compilable for C as well as C++ files, + so no C++-specific code or syntax can be put here. */ + +#ifndef PANDATOOLBASE_H +#define PANDATOOLBASE_H + +#include "pandabase.h" +#include "pandatoolsymbols.h" + +#endif diff --git a/pandatool/src/pandatoolbase/pandatoolsymbols.h b/pandatool/src/pandatoolbase/pandatoolsymbols.h new file mode 100644 index 00000000..e2e16b8b --- /dev/null +++ b/pandatool/src/pandatoolbase/pandatoolsymbols.h @@ -0,0 +1,35 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pandatoolsymbols.h + * @author drose + * @date 2001-04-26 + */ + +#ifndef PANDATOOLSYMBOLS_H +#define PANDATOOLSYMBOLS_H + +/* See dtoolsymbols.h for a rant on the purpose of this file. */ + +#ifdef BUILDING_ASSIMP + #define EXPCL_ASSIMP EXPORT_CLASS + #define EXPTP_ASSIMP EXPORT_TEMPL +#else + #define EXPCL_ASSIMP IMPORT_CLASS + #define EXPTP_ASSIMP IMPORT_TEMPL +#endif + +#ifdef BUILDING_PTLOADER + #define EXPCL_PTLOADER EXPORT_CLASS + #define EXPTP_PTLOADER EXPORT_TEMPL +#else + #define EXPCL_PTLOADER IMPORT_CLASS + #define EXPTP_PTLOADER IMPORT_TEMPL +#endif + +#endif diff --git a/pandatool/src/pandatoolbase/pathReplace.I b/pandatool/src/pandatoolbase/pathReplace.I new file mode 100644 index 00000000..282c37d3 --- /dev/null +++ b/pandatool/src/pandatoolbase/pathReplace.I @@ -0,0 +1,147 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pathReplace.I + * @author drose + * @date 2003-02-07 + */ + +/** + * Resets the error flag to the no-error state. had_error() will return false + * until a new error is generated. + */ +INLINE void PathReplace:: +clear_error() { + _error_flag = false; +} + +/** + * Returns true if an error was detected since the last call to clear_error(), + * false otherwise. + */ +INLINE bool PathReplace:: +had_error() const { + return _error_flag; +} + +/** + * Removes all the patterns from the specification. + */ +INLINE void PathReplace:: +clear() { + clear_error(); + _entries.clear(); +} + +/** + * Adds the indicated original/replace pattern to the specification. If a + * filename is encountered whose initial prefix matches the indicated + * orig_prefix, that prefix will be replaced with replacement_prefix. + */ +INLINE void PathReplace:: +add_pattern(const std::string &orig_prefix, const std::string &replacement_prefix) { + _entries.push_back(Entry(orig_prefix, replacement_prefix)); +} + +/** + * Returns the number of original/replace patterns that have been added. + */ +INLINE int PathReplace:: +get_num_patterns() const { + return _entries.size(); +} + +/** + * Returns the original prefix associated with the nth pattern. + */ +INLINE const std::string &PathReplace:: +get_orig_prefix(int n) const { + nassertr(n >= 0 && n < (int)_entries.size(), _entries[0]._orig_prefix); + return _entries[n]._orig_prefix; +} + +/** + * Returns the replacement prefix associated with the nth pattern. + */ +INLINE const std::string &PathReplace:: +get_replacement_prefix(int n) const { + nassertr(n >= 0 && n < (int)_entries.size(), _entries[0]._replacement_prefix); + return _entries[n]._replacement_prefix; +} + +/** + * Returns true if the PathReplace object specifies no action, or false if + * convert_path() may do something. + */ +INLINE bool PathReplace:: +is_empty() const { + return (_entries.empty() && _path.is_empty() && _path_store == PS_keep); +} + +/** + * Calls match_path() followed by store_path(), to replace the initial prefix + * and then convert the file for storing, as the user indicated. + */ +INLINE Filename PathReplace:: +convert_path(const Filename &orig_filename, const DSearchPath &additional_path) { + Filename fullpath, outpath; + full_convert_path(orig_filename, additional_path, fullpath, outpath); + return outpath; +} + +/** + * + */ +INLINE PathReplace::Component:: +Component(const std::string &component) : + _orig_prefix(component), + _double_star(component == "**") +{ +} + +/** + * + */ +INLINE PathReplace::Component:: +Component(const PathReplace::Component ©) : + _orig_prefix(copy._orig_prefix), + _double_star(copy._double_star) +{ +} + +/** + * + */ +INLINE void PathReplace::Component:: +operator = (const PathReplace::Component ©) { + _orig_prefix = copy._orig_prefix; + _double_star = copy._double_star; +} + +/** + * + */ +INLINE PathReplace::Entry:: +Entry(const PathReplace::Entry ©) : + _orig_prefix(copy._orig_prefix), + _orig_components(copy._orig_components), + _is_local(copy._is_local), + _replacement_prefix(copy._replacement_prefix) +{ +} + +/** + * + */ +INLINE void PathReplace::Entry:: +operator = (const PathReplace::Entry ©) { + _orig_prefix = copy._orig_prefix; + _orig_components = copy._orig_components; + _is_local = copy._is_local; + _replacement_prefix = copy._replacement_prefix; +} diff --git a/pandatool/src/pandatoolbase/pathReplace.cxx b/pandatool/src/pandatoolbase/pathReplace.cxx new file mode 100644 index 00000000..b3b6bcb4 --- /dev/null +++ b/pandatool/src/pandatoolbase/pathReplace.cxx @@ -0,0 +1,554 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pathReplace.cxx + * @author drose + * @date 2003-02-07 + */ + +#include "pathReplace.h" +#include "config_putil.h" +#include "config_pandatoolbase.h" +#include "indent.h" +#include "virtualFileSystem.h" + +/** + * + */ +PathReplace:: +PathReplace() { + _path_store = PS_keep; + _copy_files = false; + _noabs = false; + _exists = false; + _error_flag = false; +} + +/** + * + */ +PathReplace:: +~PathReplace() { +} + +/** + * Looks for a match for the given filename among all the replacement + * patterns, and returns the first match found. If additional_path is + * nonempty, it is an additional search path on which to look for the file. + * The model_path is always implicitly searched. + */ +Filename PathReplace:: +match_path(const Filename &orig_filename, + const DSearchPath &additional_path) { + Filename match; + bool got_match = false; + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + Entries::const_iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + const Entry &entry = (*ei); + Filename new_filename; + if (entry.try_match(orig_filename, new_filename)) { + // The prefix matches. Save the resulting filename for posterity. + got_match = true; + match = new_filename; + + if (new_filename.is_fully_qualified()) { + // If the resulting filename is fully qualified, it's a match if and + // only if it exists. + if (vfs->exists(new_filename)) { + return new_filename; + } + + } else { + // Otherwise, if it's a relative filename, attempt to look it up on + // the search path. + if (vfs->resolve_filename(new_filename, _path) || + vfs->resolve_filename(new_filename, additional_path) || + vfs->resolve_filename(new_filename, get_model_path())) { + // Found it! + if (_path_store == PS_keep) { + // If we asked to "keep" the pathname, we return the matched path, + // but not the found path. + return match; + } else { + // Otherwise, we return the actual, found path. + return new_filename; + } + } + } + + // The prefix matched, but it didn't exist. Keep looking. + } + } + + // The file couldn't be found anywhere. Did we at least get any prefix + // match? + if (got_match) { + if (_exists) { + _error_flag = true; + pandatoolbase_cat.error() + << "File does not exist: " << match << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "File does not exist: " << match << "\n"; + } + + return match; + } + + if (!orig_filename.is_local()) { + // Ok, we didn't match any specified prefixes. If the file is an absolute + // pathname and we have _noabs set, that's an error. + if (_noabs) { + _error_flag = true; + pandatoolbase_cat.error() + << "Absolute pathname: " << orig_filename << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "Absolute pathname: " << orig_filename << "\n"; + } + } + + // Well, we still haven't found it; look it up on the search path as is. + if (_path_store != PS_keep) { + Filename new_filename = orig_filename; + if (vfs->resolve_filename(new_filename, _path) || + vfs->resolve_filename(new_filename, additional_path) || + vfs->resolve_filename(new_filename, get_model_path())) { + // Found it! + return new_filename; + } + } + + // Nope, couldn't find anything. This is an error, but just return the + // original filename. + if (_exists) { + _error_flag = true; + pandatoolbase_cat.error() + << "File does not exist: " << orig_filename << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "File does not exist: " << orig_filename << "\n"; + } + return orig_filename; +} + +/** + * Given a path to an existing filename, converts it as specified in the + * _path_store and or _path_directory properties to a form suitable for + * storing in an output file. + */ +Filename PathReplace:: +store_path(const Filename &orig_filename) { + if (orig_filename.empty()) { + return orig_filename; + } + + if (_path_directory.is_local()) { + _path_directory.make_absolute(); + } + Filename filename = orig_filename; + + if (_copy_files) { + copy_this_file(filename); + } + + switch (_path_store) { + case PS_relative: + filename.make_absolute(); + filename.make_relative_to(_path_directory); + break; + + case PS_absolute: + filename.make_absolute(); + break; + + case PS_rel_abs: + filename.make_absolute(); + filename.make_relative_to(_path_directory, false); + break; + + case PS_strip: + filename = filename.get_basename(); + break; + + case PS_keep: + break; + + case PS_invalid: + break; + } + + return filename; +} + +/** + * Converts the input path into two different forms: A resolved path, and an + * output path. The resolved path is an absolute path if at all possible. + * The output path is in the form specified by the -ps path store option. + */ +void PathReplace:: +full_convert_path(const Filename &orig_filename, + const DSearchPath &additional_path, + Filename &resolved_path, + Filename &output_path) { + if (_path_directory.is_local()) { + _path_directory.make_absolute(); + } + + Filename match; + bool got_match = false; + + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + + Entries::const_iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + const Entry &entry = (*ei); + Filename new_filename; + if (entry.try_match(orig_filename, new_filename)) { + // The prefix matches. Save the resulting filename for posterity. + got_match = true; + match = new_filename; + + if (new_filename.is_fully_qualified()) { + // If the resulting filename is fully qualified, it's a match if and + // only if it exists. + if (vfs->exists(new_filename)) { + resolved_path = new_filename; + goto calculate_output_path; + } + + } else { + // Otherwise, if it's a relative filename, attempt to look it up on + // the search path. + if (vfs->resolve_filename(new_filename, _path) || + vfs->resolve_filename(new_filename, additional_path) || + vfs->resolve_filename(new_filename, get_model_path())) { + // Found it! + resolved_path = new_filename; + goto calculate_output_path; + } + } + + // The prefix matched, but it didn't exist. Keep looking. + } + } + + // The file couldn't be found anywhere. Did we at least get any prefix + // match? + if (got_match) { + if (_exists) { + _error_flag = true; + pandatoolbase_cat.error() + << "File does not exist: " << match << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "File does not exist: " << match << "\n"; + } + + resolved_path = match; + goto calculate_output_path; + } + + if (!orig_filename.is_local()) { + // Ok, we didn't match any specified prefixes. If the file is an absolute + // pathname and we have _noabs set, that's an error. + if (_noabs) { + _error_flag = true; + pandatoolbase_cat.error() + << "Absolute pathname: " << orig_filename << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "Absolute pathname: " << orig_filename << "\n"; + } + } + + // Well, we still haven't found it; look it up on the search path as is. + { + Filename new_filename = orig_filename; + if (vfs->resolve_filename(new_filename, _path) || + vfs->resolve_filename(new_filename, additional_path) || + vfs->resolve_filename(new_filename, get_model_path())) { + // Found it! + match = orig_filename; + resolved_path = new_filename; + goto calculate_output_path; + } + } + + // Nope, couldn't find anything. This is an error, but just return the + // original filename. + if (_exists) { + _error_flag = true; + pandatoolbase_cat.error() + << "File does not exist: " << orig_filename << "\n"; + } else if (pandatoolbase_cat.is_debug()) { + pandatoolbase_cat.debug() + << "File does not exist: " << orig_filename << "\n"; + } + match = orig_filename; + resolved_path = orig_filename; + + // To calculate the output path, we need two inputs: the match, and the + // resolved path. Which one is used depends upon the path-store mode. + calculate_output_path: + + if (_copy_files) { + if (copy_this_file(resolved_path)) { + match = resolved_path; + } + } + + switch (_path_store) { + case PS_relative: + if (resolved_path.empty()) + output_path = resolved_path; + else { + output_path = resolved_path; + output_path.make_absolute(); + output_path.make_relative_to(_path_directory); + } + break; + + case PS_absolute: + if (resolved_path.empty()) + output_path = resolved_path; + else { + output_path = resolved_path; + output_path.make_absolute(); + } + break; + + case PS_rel_abs: + if (resolved_path.empty()) + output_path = resolved_path; + else { + output_path = resolved_path; + output_path.make_absolute(); + output_path.make_relative_to(_path_directory, false); + } + break; + + case PS_strip: + output_path = match.get_basename(); + break; + + case PS_keep: + output_path = match; + break; + + case PS_invalid: + output_path = ""; + break; + } +} + +/** + * + */ +void PathReplace:: +write(std::ostream &out, int indent_level) const { + Entries::const_iterator ei; + for (ei = _entries.begin(); ei != _entries.end(); ++ei) { + indent(out, indent_level) + << "-pr " << (*ei)._orig_prefix << "=" + << (*ei)._replacement_prefix << "\n"; + } + int num_directories = _path.get_num_directories(); + for (int i = 0; i < num_directories; i++) { + indent(out, indent_level) + << "-pp " << _path.get_directory(i) << "\n"; + } + indent(out, indent_level) + << "-ps " << _path_store << "\n"; + + // The path directory is only relevant if _path_store is rel or rel_abs. + switch (_path_store) { + case PS_relative: + case PS_rel_abs: + indent(out, indent_level) + << "-pd " << _path_directory << "\n"; + + default: + break; + } + + if (_copy_files) { + indent(out, indent_level) + << "-pc " << _copy_into_directory << "\n"; + } + + if (_noabs) { + indent(out, indent_level) + << "-noabs\n"; + } +} + +/** + * Copies the indicated file into the copy_into_directory, and adjusts + * filename to reference the new location. Returns true if the copy is made + * and the filename is changed, false otherwise. + */ +bool PathReplace:: +copy_this_file(Filename &filename) { + if (_copy_into_directory.is_local()) { + _copy_into_directory = Filename(_path_directory, _copy_into_directory); + } + + Copied::iterator ci = _orig_to_target.find(filename); + if (ci != _orig_to_target.end()) { + // This file has already been successfully copied, so we can quietly + // return its new target filename. + if (filename != (*ci).second) { + filename = (*ci).second; + return true; + } + return false; + } + + Filename target_filename(_copy_into_directory, filename.get_basename()); + ci = _target_to_orig.find(target_filename); + if (ci != _target_to_orig.end()) { + if ((*ci).second != filename) { + _error_flag = true; + pandatoolbase_cat.error() + << "Filename conflict! Both " << (*ci).second << " and " + << filename << " map to " << target_filename << "\n"; + } + + // Don't copy this one. + _orig_to_target[filename] = filename; + return false; + } + + _orig_to_target[filename] = target_filename; + _target_to_orig[target_filename] = filename; + + // Make the copy. + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + vfs->make_directory_full(_copy_into_directory); + if (!vfs->copy_file(filename, target_filename)) { + _error_flag = true; + pandatoolbase_cat.error() + << "Cannot copy file from " << filename << " to " << target_filename + << "\n"; + _orig_to_target[filename] = filename; + return false; + } + + filename = target_filename; + return true; +} + +/** + * + */ +PathReplace::Entry:: +Entry(const std::string &orig_prefix, const std::string &replacement_prefix) : + _orig_prefix(orig_prefix), + _replacement_prefix(replacement_prefix) +{ + // Eliminate trailing slashes; they're implicit. + if (_orig_prefix.length() > 1 && + _orig_prefix[_orig_prefix.length() - 1] == '/') { + _orig_prefix = _orig_prefix.substr(0, _orig_prefix.length() - 1); + } + if (_replacement_prefix.length() > 1 && + _replacement_prefix[_replacement_prefix.length() - 1] == '/') { + _replacement_prefix = _replacement_prefix.substr(0, _replacement_prefix.length() - 1); + } + + Filename filename(_orig_prefix); + _is_local = filename.is_local(); + + vector_string components; + filename.extract_components(components); + vector_string::const_iterator ci; + for (ci = components.begin(); ci != components.end(); ++ci) { + _orig_components.push_back(Component(*ci)); + } +} + +/** + * Considers whether the indicated filename matches this entry's prefix. If + * so, switches the prefix and stores the result in new_filename, and returns + * true; otherwise, returns false. + */ +bool PathReplace::Entry:: +try_match(const Filename &filename, Filename &new_filename) const { + if (_is_local != filename.is_local()) { + return false; + } + vector_string components; + filename.extract_components(components); + size_t mi = r_try_match(components, 0, 0); + if (mi == 0) { + // Sorry, no match. + return false; + } + + // We found a match. Construct the replacement string. + std::string result = _replacement_prefix; + while (mi < components.size()) { + if (!result.empty()) { + result += '/'; + } + result += components[mi]; + ++mi; + } + new_filename = result; + return true; +} + +/** + * The recursive implementation of try_match(). Actually, this is doubly- + * recursive, to implement the "**" feature. + * + * The return value is the number of the "components" vector that successfully + * matched against all of the orig_components. (It's a variable number + * because there might be one or more "**" entries.) + */ +size_t PathReplace::Entry:: +r_try_match(const vector_string &components, size_t oi, size_t ci) const { + if (oi >= _orig_components.size()) { + // If we ran out of user-supplied components, we're done. + return ci; + } + if (ci >= components.size()) { + // If we reached the end of the string, but we still have user-supplied + // components, we failed. (Arguably there should be a special case here + // for a user-supplied string that ends in "**", but I don't think the + // user ever wants to match the complete string.) + return 0; + } + + const Component &orig_component = _orig_components[oi]; + if (orig_component._double_star) { + // If we have a double star, first consider the match if it were expanded + // as far as possible. + size_t mi = r_try_match(components, oi, ci + 1); + if (mi != 0) { + return mi; + } + + // Then try the match as if it there were no double star entry. + return r_try_match(components, oi + 1, ci); + } + + // We don't have a double star, it's just a one-for-one component entry. + // Does it match? + if (orig_component._orig_prefix.matches(components[ci])) { + // It does! Keep going. + return r_try_match(components, oi + 1, ci + 1); + } + + // It doesn't match, sorry. + return 0; +} diff --git a/pandatool/src/pandatoolbase/pathReplace.h b/pandatool/src/pandatoolbase/pathReplace.h new file mode 100644 index 00000000..edc69abc --- /dev/null +++ b/pandatool/src/pandatoolbase/pathReplace.h @@ -0,0 +1,127 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pathReplace.h + * @author drose + * @date 2003-02-07 + */ + +#ifndef PATHREPLACE_H +#define PATHREPLACE_H + +#include "pandatoolbase.h" +#include "pathStore.h" +#include "referenceCount.h" +#include "globPattern.h" +#include "filename.h" +#include "dSearchPath.h" +#include "pvector.h" +#include "pmap.h" + +/** + * This encapsulates the user's command-line request to replace existing, + * incorrect pathnames to models and textures from a file with correct + * pathnames. It corresponds to a sequence of -pr command-line options, as + * well as the -pp option. + * + * This can also go the next step, which is to convert a known file into a + * suitable form for storing in a model file. In this capacity, it + * corresponds to the -ps and -pd options. + */ +class PathReplace : public ReferenceCount { +public: + PathReplace(); + ~PathReplace(); + + INLINE void clear_error(); + INLINE bool had_error() const; + + INLINE void clear(); + INLINE void add_pattern(const std::string &orig_prefix, const std::string &replacement_prefix); + + INLINE int get_num_patterns() const; + INLINE const std::string &get_orig_prefix(int n) const; + INLINE const std::string &get_replacement_prefix(int n) const; + + INLINE bool is_empty() const; + + Filename match_path(const Filename &orig_filename, + const DSearchPath &additional_path = DSearchPath()); + Filename store_path(const Filename &orig_filename); + + INLINE Filename convert_path(const Filename &orig_filename, + const DSearchPath &additional_path = DSearchPath()); + + void full_convert_path(const Filename &orig_filename, + const DSearchPath &additional_path, + Filename &resolved_path, + Filename &output_path); + + void write(std::ostream &out, int indent_level = 0) const; + +public: + // This is used (along with _entries) to support match_path(). + DSearchPath _path; + + // These are used to support store_path(). + PathStore _path_store; + Filename _path_directory; + bool _copy_files; + Filename _copy_into_directory; + + // If this is this true, then the error flag is set (see had_error() and + // clear_error()) if any Filename passed to match_path() or convert_path(), + // and unmatched by one of the prefixes, happens to be an absolute pathname. + bool _noabs; + + // If this is true, then the error flag is set if any Filename passed to + // match_path() or convert_path() cannot be found. + bool _exists; + +private: + bool copy_this_file(Filename &filename); + + class Component { + public: + INLINE Component(const std::string &component); + INLINE Component(const Component ©); + INLINE void operator = (const Component ©); + + GlobPattern _orig_prefix; + bool _double_star; + }; + typedef pvector Components; + + class Entry { + public: + Entry(const std::string &orig_prefix, const std::string &replacement_prefix); + INLINE Entry(const Entry ©); + INLINE void operator = (const Entry ©); + + bool try_match(const Filename &filename, Filename &new_filename) const; + size_t r_try_match(const vector_string &components, size_t oi, size_t ci) const; + + std::string _orig_prefix; + Components _orig_components; + bool _is_local; + std::string _replacement_prefix; + }; + + typedef pvector Entries; + Entries _entries; + + bool _error_flag; + + typedef pmap Copied; + Copied _orig_to_target; + Copied _target_to_orig; +}; + +#include "pathReplace.I" + +#endif diff --git a/pandatool/src/pandatoolbase/pathStore.cxx b/pandatool/src/pandatoolbase/pathStore.cxx new file mode 100644 index 00000000..dffacfe6 --- /dev/null +++ b/pandatool/src/pandatoolbase/pathStore.cxx @@ -0,0 +1,81 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pathStore.cxx + * @author drose + * @date 2003-02-10 + */ + +#include "pathStore.h" + +#include "string_utils.h" +#include "pnotify.h" + +/** + * Returns the string corresponding to this method. + */ +std::string +format_path_store(PathStore store) { + switch (store) { + case PS_invalid: + return "invalid"; + + case PS_relative: + return "relative"; + + case PS_absolute: + return "absolute"; + + case PS_rel_abs: + return "rel_abs"; + + case PS_strip: + return "strip"; + + case PS_keep: + return "keep"; + } + nout << "**unexpected PathStore value: (" << (int)store << ")**"; + return "**"; +} + +/** + * + */ +std::ostream & +operator << (std::ostream &out, PathStore store) { + return out << format_path_store(store); +} + +/** + * Stores from a string, as might be input by the user, to one of the known + * PathStore types. Returns PS_invalid if the string is unknown. + */ +PathStore +string_path_store(const std::string &str) { + if (cmp_nocase(str, "relative") == 0 || + cmp_nocase(str, "rel") == 0) { + return PS_relative; + + } else if (cmp_nocase(str, "absolute") == 0 || + cmp_nocase(str, "abs") == 0) { + return PS_absolute; + + } else if (cmp_nocase_uh(str, "rel_abs") == 0) { + return PS_rel_abs; + + } else if (cmp_nocase(str, "strip") == 0) { + return PS_strip; + + } else if (cmp_nocase(str, "keep") == 0) { + return PS_keep; + + } else { + return PS_invalid; + } +} diff --git a/pandatool/src/pandatoolbase/pathStore.h b/pandatool/src/pandatoolbase/pathStore.h new file mode 100644 index 00000000..30440b70 --- /dev/null +++ b/pandatool/src/pandatoolbase/pathStore.h @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pathStore.h + * @author drose + * @date 2003-02-10 + */ + +#ifndef PATHSTORE_H +#define PATHSTORE_H + +#include "pandatoolbase.h" + +/** + * This enumerated type lists the methods by which a filename path might be + * mangled before storing in a destination file. + */ +enum PathStore { + PS_invalid, // Never use this. + PS_relative, // Make relative to a user-specified directory. + PS_absolute, // Make absolute. + PS_rel_abs, // Make relative if within the directory, otherwise absolute. + PS_strip, // Strip prefix and just store the basename. + PS_keep, // Don't change the filename at all. +}; + +std::string format_path_store(PathStore unit); + +std::ostream &operator << (std::ostream &out, PathStore unit); +PathStore string_path_store(const std::string &str); + +#endif diff --git a/pandatool/src/pfmprogs/CMakeLists.txt b/pandatool/src/pfmprogs/CMakeLists.txt new file mode 100644 index 00000000..4176ad2c --- /dev/null +++ b/pandatool/src/pfmprogs/CMakeLists.txt @@ -0,0 +1,13 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(pfm-bba + config_pfmprogs.cxx config_pfmprogs.h + pfmBba.cxx pfmBba.h) +target_link_libraries(pfm-bba p3progbase) +install(TARGETS pfm-bba EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(pfm-trans pfmTrans.cxx pfmTrans.h) +target_link_libraries(pfm-trans p3progbase) +install(TARGETS pfm-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/pfmprogs/config_pfmprogs.cxx b/pandatool/src/pfmprogs/config_pfmprogs.cxx new file mode 100644 index 00000000..afa49ef9 --- /dev/null +++ b/pandatool/src/pfmprogs/config_pfmprogs.cxx @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_pfmprogs.cxx + * @author drose + * @date 2010-12-23 + */ + +#include "config_pfmprogs.h" + +#include "dconfig.h" + +Configure(config_pfmprogs); +NotifyCategoryDef(pfm, ""); + +ConfigVariableDouble pfm_bba_dist +("pfm-bba-dist", "0.2 0.05", + PRC_DESC("Specifies the point_dist and sample_radius, in UV space, for " + "compute bba files with pfm_trans.")); + +ConfigureFn(config_pfmprogs) { + init_libpfm(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libpfm() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; +} diff --git a/pandatool/src/pfmprogs/config_pfmprogs.h b/pandatool/src/pfmprogs/config_pfmprogs.h new file mode 100644 index 00000000..98777496 --- /dev/null +++ b/pandatool/src/pfmprogs/config_pfmprogs.h @@ -0,0 +1,29 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_pfmprogs.h + * @author drose + * @date 2010-12-23 + */ + +#ifndef CONFIG_PFM_H +#define CONFIG_PFM_H + +#include "pandatoolbase.h" + +#include "notifyCategoryProxy.h" +#include "configVariableBool.h" +#include "configVariableDouble.h" + +NotifyCategoryDeclNoExport(pfm); + +extern ConfigVariableDouble pfm_bba_dist; + +extern void init_libpfm(); + +#endif diff --git a/pandatool/src/pfmprogs/pfmBba.cxx b/pandatool/src/pfmprogs/pfmBba.cxx new file mode 100644 index 00000000..ce0851a4 --- /dev/null +++ b/pandatool/src/pfmprogs/pfmBba.cxx @@ -0,0 +1,142 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pfmBba.cxx + * @author drose + * @date 2011-03-02 + */ + +#include "pfmBba.h" +#include "config_pfmprogs.h" +#include "pfmFile.h" + +/** + * + */ +PfmBba:: +PfmBba() { + set_program_brief("generate .bba files from .pfm files"); + set_program_description + ("pfm-bba generates a .bba file from a .pfm file that lists the " + "planar bounding volume of the pfm's internal data."); + + add_option + ("z", "", 0, + "Treats (0,0,0) in the pfm file as a special don't-touch value.", + &PfmBba::dispatch_none, &_got_zero_special); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting bba file will be written.", + &PfmBba::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +/** + * + */ +void PfmBba:: +run() { + Filenames::const_iterator fi; + for (fi = _input_filenames.begin(); fi != _input_filenames.end(); ++fi) { + PfmFile file; + if (!file.read(*fi)) { + nout << "Cannot read " << *fi << "\n"; + exit(1); + } + if (!process_pfm(*fi, file)) { + exit(1); + } + } +} + +/** + * Handles a single pfm file. + */ +bool PfmBba:: +process_pfm(const Filename &input_filename, PfmFile &file) { + file.set_zero_special(_got_zero_special); + + Filename bba_filename; + if (_got_output_filename) { + bba_filename = _output_filename; + } else { + bba_filename = input_filename; + bba_filename.set_extension("bba"); + } + + if (!bba_filename.empty()) { + bba_filename.set_text(); + PT(BoundingHexahedron) bounds = file.compute_planar_bounds(LPoint2f(0.5, 0.5), pfm_bba_dist[0], pfm_bba_dist[1], false); + nassertr(bounds != nullptr, false); + + pofstream out; + if (!bba_filename.open_write(out)) { + std::cerr << "Unable to open " << bba_filename << "\n"; + return false; + } + + LPoint3 points[8]; + for (int i = 0; i < 8; ++i) { + points[i] = bounds->get_point(i); + } + + // Experiment with expanding the back wall backwards. + /* + LPlane plane(points[0], points[1], points[2]); + LVector3 normal = plane.get_normal(); + + static const PN_stdfloat scale = 20.0f; + normal *= scale; + points[0] += normal; + points[1] += normal; + points[2] += normal; + points[3] += normal; + */ + + for (int i = 0; i < 8; ++i) { + const LPoint3 &p = points[i]; + out << p[0] << "," << p[1] << "," << p[2] << "\n"; + } + } + + return true; +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool PfmBba:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the pfm file(s) to read on the command line.\n"; + return false; + } + + if (args.size() > 1 && _got_output_filename) { + nout << "Cannot use -o when multiple pfm files are specified.\n"; + return false; + } + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + _input_filenames.push_back(Filename::from_os_specific(*ai)); + } + + return true; +} + + +int main(int argc, char *argv[]) { + PfmBba prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/pfmprogs/pfmBba.h b/pandatool/src/pfmprogs/pfmBba.h new file mode 100644 index 00000000..4d01ca64 --- /dev/null +++ b/pandatool/src/pfmprogs/pfmBba.h @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pfmBba.h + * @author drose + * @date 2011-03-02 + */ + +#ifndef PFMBBA_H +#define PFMBBA_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "filename.h" +#include "pvector.h" +#include "nodePath.h" +#include "luse.h" + +class PfmFile; + +/** + * Generates a bounding-box description of a pfm file. + */ +class PfmBba : public ProgramBase { +public: + PfmBba(); + + void run(); + bool process_pfm(const Filename &input_filename, PfmFile &file); + +protected: + virtual bool handle_args(Args &args); + +private: + typedef pvector Filenames; + Filenames _input_filenames; + + bool _got_zero_special; + bool _got_output_filename; + Filename _output_filename; +}; + +#endif diff --git a/pandatool/src/pfmprogs/pfmTrans.cxx b/pandatool/src/pfmprogs/pfmTrans.cxx new file mode 100644 index 00000000..ffb99b24 --- /dev/null +++ b/pandatool/src/pfmprogs/pfmTrans.cxx @@ -0,0 +1,512 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pfmTrans.cxx + * @author drose + * @date 2010-12-23 + */ + +#include "pfmTrans.h" +#include "config_pfmprogs.h" +#include "pfmFile.h" +#include "pfmVizzer.h" +#include "texture.h" +#include "texturePool.h" +#include "pointerTo.h" +#include "string_utils.h" +#include "pandaFileStream.h" + +using std::string; + +/** + * + */ +PfmTrans:: +PfmTrans() { + _no_data_nan_num_channels = 0; + _got_transform = false; + _transform = LMatrix4::ident_mat(); + _rotate = 0; + + add_transform_options(); + + set_program_brief("transform .pfm files"); + set_program_description + ("pfm-trans reads an pfm file and transforms it, filters it, " + "operates on it, writing the output to another pfm file. A pfm " + "file contains a 2-d table of floating-point values."); + + add_option + ("z", "", 0, + "Treats (0,0,0) in the pfm file as a special don't-touch value.", + &PfmTrans::dispatch_none, &_got_zero_special); + + add_option + ("nan", "num_channels", 0, + "Treats a NaN in any of the first num_channels channels as a special don't-touch value.", + &PfmTrans::dispatch_int, &_got_no_data_nan, &_no_data_nan_num_channels); + + add_option + ("resize", "width,height", 0, + "Resamples the pfm file to scale it to the indicated grid size. " + "A simple box filter is applied during the scale. Don't confuse this " + "with -TS, which scales the individual point values, but doesn't " + "change the number of points.", + &PfmTrans::dispatch_int_pair, &_got_resize, &_resize); + + add_option + ("crop", "xbegin,xend,ybegin,yend", 0, + "Crops the pfm file to the indicated subregion.", + &PfmTrans::dispatch_int_quad, &_got_crop, &_crop); + + add_option + ("autocrop", "", 0, + "Automatically crops to the smallest possible rectangle that includes " + "all points. Requires -z or -nan.", + &PfmTrans::dispatch_none, &_got_autocrop); + + add_option + ("rotate", "degrees", 0, + "Rotates the pfm file the specified number of degrees counterclockwise, " + "which must be a multiple of 90.", + &PfmTrans::dispatch_int, nullptr, &_rotate); + + add_option + ("mirror_x", "", 0, + "Flips the pfm file about the x axis.", + &PfmTrans::dispatch_none, &_got_mirror_x); + + add_option + ("mirror_y", "", 0, + "Flips the pfm file about the y axis.", + &PfmTrans::dispatch_none, &_got_mirror_y); + + add_option + ("o", "filename", 50, + "Specify the filename to which the resulting pfm file will be written. " + "This is only valid when there is only one input pfm file on the command " + "line. If you want to process multiple files simultaneously, you must " + "use -d.", + &PfmTrans::dispatch_filename, &_got_output_filename, &_output_filename); + + add_option + ("d", "dirname", 50, + "Specify the name of the directory in which to write the processed pfm " + "files. If you are processing only one pfm file, this may be omitted " + "in lieu of the -o option.", + &PfmTrans::dispatch_filename, &_got_output_dirname, &_output_dirname); + + add_option + ("vis", "filename.bam", 60, + "Generates a bam file that represents a visualization of the pfm file " + "as a 3-D geometric mesh. If -vistex is specified, the mesh is " + "textured.", + &PfmTrans::dispatch_filename, &_got_vis_filename, &_vis_filename); + + add_option + ("visinv", "", 60, + "Inverts the visualization, generating a uniform 2-d mesh with the " + "3-d depth values encoded in the texture coordinates.", + &PfmTrans::dispatch_none, &_got_vis_inverse); + + add_option + ("vis2d", "", 60, + "Respect only the first two components of each depth value, ignoring z.", + &PfmTrans::dispatch_none, &_got_vis_2d); + + add_option + ("vistex", "texture.jpg", 60, + "Specifies the name of the texture to apply to the visualization.", + &PfmTrans::dispatch_filename, &_got_vistex_filename, &_vistex_filename); + + add_option + ("ls", "filename.txt", 60, + "Lists the points in the file to the indicated text file.", + &PfmTrans::dispatch_filename, &_got_ls_filename, &_ls_filename); +} + + +/** + * + */ +void PfmTrans:: +run() { + if ((int)(_rotate / 90) * 90 != _rotate) { + nout << "-rotate can only accept a multiple of 90 degrees.\n"; + exit(1); + } + + if (_got_vis_filename) { + _mesh_root = NodePath("mesh_root"); + } + + Filenames::const_iterator fi; + for (fi = _input_filenames.begin(); fi != _input_filenames.end(); ++fi) { + PfmFile file; + if (!file.read(*fi)) { + nout << "Cannot read " << *fi << "\n"; + exit(1); + } + if (!process_pfm(*fi, file)) { + exit(1); + } + } + + if (_got_vis_filename) { + _mesh_root.write_bam_file(_vis_filename); + } +} + +/** + * Handles a single pfm file. + */ +bool PfmTrans:: +process_pfm(const Filename &input_filename, PfmFile &file) { + PfmVizzer vizzer(file); + if (_got_no_data_nan) { + file.set_no_data_nan(_no_data_nan_num_channels); + } else if (_got_zero_special) { + file.set_zero_special(true); + } + vizzer.set_vis_inverse(_got_vis_inverse); + vizzer.set_vis_2d(_got_vis_2d); + + if (_got_autocrop) { + _got_crop = file.calc_autocrop(_crop[0], _crop[1], _crop[2], _crop[3]); + } + + if (_got_crop) { + file.apply_crop(_crop[0], _crop[1], _crop[2], _crop[3]); + } + + if (_got_resize) { + file.resize(_resize[0], _resize[1]); + } + + if (_rotate != 0) { + int r = (_rotate / 90) % 4; + if (r < 0) { + r += 4; + } + switch (r) { + case 0: + break; + case 1: + // Rotate 90 degrees ccw. + file.flip(true, false, true); + break; + case 2: + // Rotate 180 degrees. + + // Not sure right now why we can't flip both axes at once. But it works + // if we do one at a time. file.flip(true, true, false); + file.flip(true, false, false); + file.flip(false, true, false); + break; + case 3: + // Rotate 90 degrees cw. + file.flip(false, true, true); + break; + default: + nassertr(false, false); + } + } + + if (_got_mirror_x) { + file.flip(true, false, false); + } + + if (_got_mirror_y) { + file.flip(false, true, false); + } + + if (_got_transform) { + file.xform(LCAST(PN_float32, _transform)); + } + + if (_got_vis_filename) { + NodePath mesh = vizzer.generate_vis_mesh(PfmVizzer::MF_both); + if (_got_vistex_filename) { + PT(Texture) tex = TexturePool::load_texture(_vistex_filename); + if (tex == nullptr) { + nout << "Couldn't find " << _vistex_filename << "\n"; + } else { + tex->set_minfilter(SamplerState::FT_linear_mipmap_linear); + mesh.set_texture(tex); + if (tex->has_alpha(tex->get_format())) { + mesh.set_transparency(TransparencyAttrib::M_dual); + } + } + } + mesh.set_name(input_filename.get_basename_wo_extension()); + mesh.reparent_to(_mesh_root); + } + + if (_got_ls_filename) { + pofstream out; + _ls_filename.set_text(); + if (_ls_filename.open_write(out, true)) { + for (int yi = 0; yi < file.get_y_size(); ++yi) { + for (int xi = 0; xi < file.get_x_size(); ++xi) { + if (file.has_point(xi, yi)) { + out << "(" << xi << ", " << yi << "):"; + for (int ci = 0; ci < file.get_num_channels(); ++ci) { + out << " " << file.get_channel(xi, yi, ci); + } + out << "\n"; + } + } + } + } + } + + Filename output_filename; + if (_got_output_filename) { + output_filename = _output_filename; + } else if (_got_output_dirname) { + output_filename = Filename(_output_dirname, input_filename.get_basename()); + } + + if (!output_filename.empty()) { + return file.write(output_filename); + } + + return true; +} + +/** + * Adds -TS, -TT, etc. as valid options for this program. If the user + * specifies one of the options on the command line, the data will be + * transformed when the egg file is written out. + */ +void PfmTrans:: +add_transform_options() { + add_option + ("TS", "sx[,sy,sz]", 49, + "Scale the model uniformly by the given factor (if only one number " + "is given) or in each axis by sx, sy, sz (if three numbers are given).", + &PfmTrans::dispatch_scale, &_got_transform, &_transform); + + add_option + ("TR", "x,y,z", 49, + "Rotate the model x degrees about the x axis, then y degrees about the " + "y axis, and then z degrees about the z axis.", + &PfmTrans::dispatch_rotate_xyz, &_got_transform, &_transform); + + add_option + ("TA", "angle,x,y,z", 49, + "Rotate the model angle degrees counterclockwise about the given " + "axis.", + &PfmTrans::dispatch_rotate_axis, &_got_transform, &_transform); + + add_option + ("TT", "x,y,z", 49, + "Translate the model by the indicated amount.\n\n" + "All transformation options (-TS, -TR, -TA, -TT) are cumulative and are " + "applied in the order they are encountered on the command line.", + &PfmTrans::dispatch_translate, &_got_transform, &_transform); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool PfmTrans:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + nout << "You must specify the pfm file(s) to read on the command line.\n"; + return false; + } + + if (_got_output_filename && args.size() == 1) { + if (_got_output_dirname) { + nout << "Cannot specify both -o and -d.\n"; + return false; + } + + } else { + if (_got_output_filename) { + nout << "Cannot use -o when multiple pfm files are specified.\n"; + return false; + } + } + + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + _input_filenames.push_back(Filename::from_os_specific(*ai)); + } + + return true; +} + +/** + * Handles -TS, which specifies a scale transform. Var is an LMatrix4. + */ +bool PfmTrans:: +dispatch_scale(const string &opt, const string &arg, void *var) { + LMatrix4 *transform = (LMatrix4 *)var; + + vector_string words; + tokenize(arg, words, ","); + + PN_stdfloat sx, sy, sz; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_stdfloat(words[0], sx) && + string_to_stdfloat(words[1], sy) && + string_to_stdfloat(words[2], sz); + + } else if (words.size() == 1) { + okflag = + string_to_stdfloat(words[0], sx); + sy = sz = sx; + } + + if (!okflag) { + nout << "-" << opt + << " requires one or three numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4::scale_mat(sx, sy, sz); + + return true; +} + +/** + * Handles -TR, which specifies a rotate transform about the three cardinal + * axes. Var is an LMatrix4. + */ +bool PfmTrans:: +dispatch_rotate_xyz(ProgramBase *self, const string &opt, const string &arg, void *var) { + PfmTrans *base = (PfmTrans *)self; + return base->ns_dispatch_rotate_xyz(opt, arg, var); +} + +/** + * Handles -TR, which specifies a rotate transform about the three cardinal + * axes. Var is an LMatrix4. + */ +bool PfmTrans:: +ns_dispatch_rotate_xyz(const string &opt, const string &arg, void *var) { + LMatrix4 *transform = (LMatrix4 *)var; + + vector_string words; + tokenize(arg, words, ","); + + LVecBase3 xyz; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_stdfloat(words[0], xyz[0]) && + string_to_stdfloat(words[1], xyz[1]) && + string_to_stdfloat(words[2], xyz[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + LMatrix4 mat = + LMatrix4::rotate_mat(xyz[0], LVector3(1.0, 0.0, 0.0)) * + LMatrix4::rotate_mat(xyz[1], LVector3(0.0, 1.0, 0.0)) * + LMatrix4::rotate_mat(xyz[2], LVector3(0.0, 0.0, 1.0)); + + *transform = (*transform) * mat; + + return true; +} + +/** + * Handles -TA, which specifies a rotate transform about an arbitrary axis. + * Var is an LMatrix4. + */ +bool PfmTrans:: +dispatch_rotate_axis(ProgramBase *self, const string &opt, const string &arg, void *var) { + PfmTrans *base = (PfmTrans *)self; + return base->ns_dispatch_rotate_axis(opt, arg, var); +} + +/** + * Handles -TA, which specifies a rotate transform about an arbitrary axis. + * Var is an LMatrix4. + */ +bool PfmTrans:: +ns_dispatch_rotate_axis(const string &opt, const string &arg, void *var) { + LMatrix4 *transform = (LMatrix4 *)var; + + vector_string words; + tokenize(arg, words, ","); + + PN_stdfloat angle; + LVecBase3 axis; + + bool okflag = false; + if (words.size() == 4) { + okflag = + string_to_stdfloat(words[0], angle) && + string_to_stdfloat(words[1], axis[0]) && + string_to_stdfloat(words[2], axis[1]) && + string_to_stdfloat(words[3], axis[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires four numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4::rotate_mat(angle, axis); + + return true; +} + +/** + * Handles -TT, which specifies a translate transform. Var is an LMatrix4. + */ +bool PfmTrans:: +dispatch_translate(const string &opt, const string &arg, void *var) { + LMatrix4 *transform = (LMatrix4 *)var; + + vector_string words; + tokenize(arg, words, ","); + + LVector3 trans; + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_stdfloat(words[0], trans[0]) && + string_to_stdfloat(words[1], trans[1]) && + string_to_stdfloat(words[2], trans[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + *transform = (*transform) * LMatrix4::translate_mat(trans); + + return true; +} + + +int main(int argc, char *argv[]) { + PfmTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/pfmprogs/pfmTrans.h b/pandatool/src/pfmprogs/pfmTrans.h new file mode 100644 index 00000000..7d791277 --- /dev/null +++ b/pandatool/src/pfmprogs/pfmTrans.h @@ -0,0 +1,83 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pfmTrans.h + * @author drose + * @date 2010-12-23 + */ + +#ifndef PFMTRANS_H +#define PFMTRANS_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "filename.h" +#include "pvector.h" +#include "nodePath.h" +#include "luse.h" + +class PfmFile; + +/** + * Operates on a pfm file. + */ +class PfmTrans : public ProgramBase { +public: + PfmTrans(); + + void run(); + bool process_pfm(const Filename &input_filename, PfmFile &file); + + void add_transform_options(); + +protected: + virtual bool handle_args(Args &args); + + static bool dispatch_scale(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_rotate_xyz(ProgramBase *self, const std::string &opt, const std::string &arg, void *var); + bool ns_dispatch_rotate_xyz(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_rotate_axis(ProgramBase *self, const std::string &opt, const std::string &arg, void *var); + bool ns_dispatch_rotate_axis(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_translate(const std::string &opt, const std::string &arg, void *var); + +private: + typedef pvector Filenames; + Filenames _input_filenames; + + bool _got_zero_special; + bool _got_no_data_nan; + int _no_data_nan_num_channels; + bool _got_vis_inverse; + bool _got_vis_2d; + bool _got_resize; + int _resize[2]; + bool _got_crop; + int _crop[4]; + bool _got_autocrop; + int _rotate; + bool _got_mirror_x; + bool _got_mirror_y; + + bool _got_output_filename; + Filename _output_filename; + bool _got_output_dirname; + Filename _output_dirname; + bool _got_vis_filename; + Filename _vis_filename; + bool _got_vistex_filename; + Filename _vistex_filename; + bool _got_ls_filename; + Filename _ls_filename; + + bool _got_transform; + LMatrix4 _transform; + + NodePath _mesh_root; +}; + +#endif diff --git a/pandatool/src/progbase/CMakeLists.txt b/pandatool/src/progbase/CMakeLists.txt new file mode 100644 index 00000000..b607968b --- /dev/null +++ b/pandatool/src/progbase/CMakeLists.txt @@ -0,0 +1,27 @@ +set(P3PROGBASE_HEADERS + programBase.h programBase.I + withOutputFile.h withOutputFile.I + wordWrapStream.h wordWrapStreamBuf.I + wordWrapStreamBuf.h +) + +set(P3PROGBASE_SOURCES + programBase.cxx withOutputFile.cxx wordWrapStream.cxx + wordWrapStreamBuf.cxx +) + +composite_sources(p3progbase P3PROGBASE_SOURCES) +add_library(p3progbase STATIC ${P3PROGBASE_HEADERS} ${P3PROGBASE_SOURCES}) +target_link_libraries(p3progbase p3pandatoolbase panda PKG::ZLIB) + +if(IOCTL_TERMINAL_WIDTH) + target_compile_definitions(p3progbase PRIVATE IOCTL_TERMINAL_WIDTH) +endif() + +install(TARGETS p3progbase + EXPORT ToolsDevel COMPONENT ToolsDevel + DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d + ARCHIVE COMPONENT ToolsDevel) +install(FILES ${P3PROGBASE_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d) diff --git a/pandatool/src/progbase/p3progbase_composite1.cxx b/pandatool/src/progbase/p3progbase_composite1.cxx new file mode 100644 index 00000000..c5960019 --- /dev/null +++ b/pandatool/src/progbase/p3progbase_composite1.cxx @@ -0,0 +1,7 @@ + +#include "programBase.cxx" +#include "withOutputFile.cxx" +#include "wordWrapStream.cxx" +#include "wordWrapStreamBuf.cxx" + + diff --git a/pandatool/src/progbase/programBase.I b/pandatool/src/progbase/programBase.I new file mode 100644 index 00000000..960bad09 --- /dev/null +++ b/pandatool/src/progbase/programBase.I @@ -0,0 +1,20 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file programBase.I + * @author drose + * @date 2000-06-28 + */ + +/** + * Formats the indicated text to stderr with the known _terminal_width. + */ +INLINE void ProgramBase:: +show_text(const std::string &text) { + show_text("", 0, text); +} diff --git a/pandatool/src/progbase/programBase.cxx b/pandatool/src/progbase/programBase.cxx new file mode 100644 index 00000000..7486c4a9 --- /dev/null +++ b/pandatool/src/progbase/programBase.cxx @@ -0,0 +1,1493 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file programBase.cxx + * @author drose + * @date 2000-02-13 + */ + +#include "programBase.h" +#include "wordWrapStream.h" + +#include "pnmFileTypeRegistry.h" +#include "indent.h" +#include "dSearchPath.h" +#include "coordinateSystem.h" +#include "dconfig.h" +#include "string_utils.h" +#include "vector_string.h" +#include "configVariableInt.h" +#include "configVariableBool.h" +#include "panda_getopt_long.h" +#include "preprocess_argv.h" +#include "pandaSystem.h" + +#include +#include +#include + +// This manifest is defined if we are running on a system (e.g. most any +// Unix) that allows us to determine the width of the terminal screen via an +// ioctl() call. It's just handy to know for formatting output nicely for the +// user. +#ifdef IOCTL_TERMINAL_WIDTH + #include + #ifndef TIOCGWINSZ + #include + #elif __APPLE__ + #include + #endif // TIOCGWINSZ +#endif // IOCTL_TERMINAL_WIDTH + +using std::cerr; +using std::cout; +using std::max; +using std::min; +using std::string; + +bool ProgramBase::SortOptionsByIndex:: +operator () (const Option *a, const Option *b) const { + if (a->_index_group != b->_index_group) { + return a->_index_group < b->_index_group; + } + return a->_sequence < b->_sequence; +} + +// This should be called at program termination just to make sure Notify gets +// properly flushed before we exit, if someone calls exit(). It's probably +// not necessary, but why not be phobic about it? +static void flush_nout() { + nout << std::flush; +} + +static ConfigVariableInt default_terminal_width +("default-terminal-width", 72, + PRC_DESC("Specify the column at which to wrap output lines " + "from pandatool-based programs, if it cannot be determined " + "automatically.")); + +static ConfigVariableBool use_terminal_width +("use-terminal-width", true, + PRC_DESC("True to try to determine the terminal width automatically from " + "the operating system, if supported; false to use the width " + "specified by default-terminal-width even if the operating system " + "appears to report a valid width.")); + +/** + * + */ +ProgramBase:: +ProgramBase(const string &name) : _name(name) { + // Set up Notify to write output to our own formatted stream. + Notify::ptr()->set_ostream_ptr(new WordWrapStream(this), true); + + // And we'll want to be sure to flush that in all normal exit cases. + atexit(&flush_nout); + + _path_replace = new PathReplace; + + // If a program never adds the path store options, the default path store is + // PS_absolute. This is the most robust solution for programs that read + // files but do not need to write them. + _path_replace->_path_store = PS_absolute; + _got_path_store = false; + _got_path_directory = false; + + _next_sequence = 0; + _sorted_options = false; + _last_newline = false; + _got_terminal_width = false; + _got_option_indent = false; + + add_option("h", "", 100, + "Display this help page.", + &ProgramBase::handle_help_option, nullptr, (void *)this); + + // It's nice to start with a blank line. + nout << "\r"; +} + +/** + * + */ +ProgramBase:: +~ProgramBase() { + // Reset Notify in case any messages get sent after our destruction--our + // stream is no longer valid. + Notify::ptr()->set_ostream_ptr(nullptr, false); +} + +/** + * Writes the program description to stderr. + */ +void ProgramBase:: +show_description() { + nout << _description << "\n"; +} + +/** + * Writes the usage line(s) to stderr. + */ +void ProgramBase:: +show_usage() { + nout << "\rUsage:\n"; + Runlines::const_iterator ri; + string prog = " " + _program_name.get_basename_wo_extension(); + + for (ri = _runlines.begin(); ri != _runlines.end(); ++ri) { + show_text(prog, prog.length() + 1, *ri); + } + nout << "\r"; +} + +/** + * Describes each of the available options to stderr. + */ +void ProgramBase:: +show_options() { + sort_options(); + if (!_got_option_indent) { + get_terminal_width(); + _option_indent = min(15, (int)(_terminal_width * 0.25)); + _got_option_indent = true; + } + + nout << "Options:\n"; + OptionsByIndex::const_iterator oi; + for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) { + const Option &opt = *(*oi); + string prefix = " -" + opt._option + " " + opt._parm_name; + show_text(prefix, _option_indent, opt._description + "\r"); + } +} + +/** + * Formats the indicated text and its prefix for output to stderr with the + * known _terminal_width. + */ +void ProgramBase:: +show_text(const string &prefix, int indent_width, string text) { + get_terminal_width(); + + // This is correct! It goes go to cerr, not to nout. Sending it to nout + // would be cyclic, since nout is redefined to map back through this + // function. + format_text(cerr, _last_newline, + prefix, indent_width, text, _terminal_width); +} + +/** + * Generates a man page in nroff syntax based on the description and options. + * This is useful when creating a man page for this utility. + */ +void ProgramBase:: +write_man_page(std::ostream &out) { + string prog = _program_name.get_basename_wo_extension(); + out << ".\\\" Automatically generated by " << prog << " -write-man\n"; + + // Format the man page title as the uppercase version of the program name, + // as per the UNIX manual conventions. + out << ".TH "; + string::const_iterator si; + for (si = _name.begin(); si != _name.end(); ++si) { + out << (char)toupper(*si); + } + + // Generate a date string for inclusion into the footer. + char date_str[256]; + date_str[0] = 0; + time_t current_time; + tm *today = nullptr; + + // This variable overrides the time we write to the footer. + const char *source_date_epoch = getenv("SOURCE_DATE_EPOCH"); + if (source_date_epoch == nullptr || source_date_epoch[0] == 0 || + (current_time = (time_t)strtoll(source_date_epoch, nullptr, 10)) <= 0) { + current_time = time(nullptr); + if (current_time != (time_t)-1) { + today = localtime(¤t_time); + } + } + else { + // Format as UTC to avoid inconsistency being introduced due to timezones. + today = gmtime(¤t_time); + } + + if (today == nullptr || 0 == strftime(date_str, 256, "%d %B %Y", today)) { + date_str[0] = 0; + } + + out << " 1 \"" << date_str << "\" \"" + << PandaSystem::get_version_string() << "\" Panda3D\n"; + + out << ".SH NAME\n"; + if (_brief.empty()) { + out << _name << "\n"; + } else { + out << _name << " \\- " << _brief << "\n"; + } + + out << ".SH SYNOPSIS\n"; + Runlines::const_iterator ri = _runlines.begin(); + if (ri != _runlines.end()) { + out << "\\fB" << prog << "\\fR " << *ri << "\n"; + ++ri; + } + + for (; ri != _runlines.end(); ++ri) { + out << ".br\n"; + out << "\\fB" << prog << "\\fR " << *ri << "\n"; + } + + out << ".SH DESCRIPTION\n"; + string::const_iterator di; + char prev = 0; + for (di = _description.begin(); di != _description.end(); ++di) { + if ((*di) == '-') { + // We have to escape hyphens. + out << "\\-"; + } else if (prev == '\n' && (*di) == '\n') { + // Indicate the start of a new paragraph. + out << ".PP\n"; + } else { + out << (char)(*di); + } + prev = (*di); + } + out << "\n"; + + out << ".SH OPTIONS\n"; + sort_options(); + OptionsByIndex::const_iterator oi; + for (oi = _options_by_index.begin(); oi != _options_by_index.end(); ++oi) { + const Option &opt = *(*oi); + out << ".TP\n"; + + if (opt._parm_name.empty()) { + out << ".B \\-" << opt._option << "\n"; + } else { + out << ".BI \"\\-" << opt._option << " \" \"" << opt._parm_name << "\"\n"; + } + out << opt._description << "\n"; + } +} + +/** + * Dispatches on each of the options on the command line, and passes the + * remaining parameters to handle_args(). If an error on the command line is + * detected, will automatically call show_usage() and exit(1). If exit_on_complete + * is not set, will return the program's exit code instead of exiting. + */ +ProgramBase::ExitCode ProgramBase:: +parse_command_line(int argc, char **argv, bool exit_on_complete) { + if (!exit_on_complete) { + // The help option should not be available in programmatic environments. + remove_option("h"); + } + + preprocess_argv(argc, argv); + + // Setting this variable to zero reinitializes the options parser This is + // only necessary for processing multiple command lines in the same program + // (mainly the MaxToEgg converter plugin) + extern int optind; + optind = 0; + +#if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_LONG_ONLY) + // We're using a Panda implementation of getopt. Let's reset that as well. + pgetopt_reset(); +#endif + + _program_name = Filename::from_os_specific(argv[0]); + int i; + for (i = 1; i < argc; i++) { + _program_args.push_back(argv[i]); + } + + if (_name.empty()) { + _name = _program_name.get_basename_wo_extension(); + } + + // Catch a special hidden option: -write-man, which causes the tool to + // generate a manual page. + if (argc > 1 && strcmp(argv[1], "-write-man") == 0) { + if (argc == 2) { + write_man_page(cout); + + } else if (argc == 3) { + if (strlen(argv[2]) == 1 && argv[2][0] == '-') { + write_man_page(cout); + + } else { + pofstream man_out(argv[2], std::ios::out | std::ios::trunc); + if (!man_out) { + cerr << "Failed to open output file " << argv[2] << "!\n"; + } + write_man_page(man_out); + man_out.close(); + } + } else { + cerr << "Invalid number of options for -write-man!\n"; + if (exit_on_complete) { + exit(1); + } + return ExitCode::EC_failure; + } + + if (exit_on_complete) { + exit(0); + } + return ExitCode::EC_clean_exit; + } + + // Build up the long options list and the short options string for + // getopt_long_only(). + pvector long_options; + string short_options; + + // We also need to build a temporary map of int index numbers to Option + // pointers. We'll pass these index numbers to GNU's getopt_long() so we + // can tell one option from another. + typedef pmap Options; + Options options; + + OptionsByName::const_iterator oi; + int next_index = 256; + + // Let's prefix the option string with "-" to tell getopt that we want it to + // tell us the post-option arguments, instead of trying to meddle with ARGC + // and ARGV (which we aren't using directly). + short_options = "-"; + + for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { + const Option &opt = (*oi).second; + + int index; + if (opt._option.length() == 1) { + // This is a "short" option; its option string consists of only one + // letter. Its index is the letter itself. + index = (int)opt._option[0]; + + short_options += opt._option; + if (!opt._parm_name.empty()) { + // This option takes an argument. + short_options += ':'; + } + } else { + // This is a "long" option; we'll assign it the next available index. + index = ++next_index; + } + + // Now add it to the GNU data structures. + struct option gopt; + gopt.name = (char *)opt._option.c_str(); + gopt.has_arg = (opt._parm_name.empty()) ? + no_argument : required_argument; + gopt.flag = nullptr; + + // Return an index into the _options_by_index array, offset by 256 so we + // don't confuse it with '?'. + gopt.val = index; + + long_options.push_back(gopt); + + options[index] = &opt; + } + + // Finally, add one more structure, all zeroes, to indicate the end of the + // options. + struct option gopt; + memset(&gopt, 0, sizeof(gopt)); + long_options.push_back(gopt); + + // We'll use this vector to save the non-option arguments. Generally, these + // will all be at the end, but with the GNU extensions, they need not be. + Args remaining_args; + + // Now call getopt_long() to actually parse the arguments. + extern char *optarg; + const struct option *long_opts = &long_options[0]; + + int flag = + getopt_long_only(argc, argv, short_options.c_str(), long_opts, nullptr); + while (flag != EOF) { + string arg; + if (optarg != nullptr) { + arg = optarg; + } + + switch (flag) { + case '?': + // Invalid option or parameter. + show_usage(); + if (exit_on_complete) { + exit(1); + } + return ExitCode::EC_failure; + + case '\x1': + // A special return value from getopt() indicating a non-option + // argument. + remaining_args.push_back(arg); + break; + + default: + { + // A normal option. Figure out which one it is. + Options::const_iterator ii; + ii = options.find(flag); + if (ii == options.end()) { + nout << "Internal error! Invalid option index returned.\n"; + abort(); + } + + const Option &opt = *(*ii).second; + bool okflag = true; + if (opt._option_function != (OptionDispatchFunction)nullptr) { + okflag = (*opt._option_function)(opt._option, arg, opt._option_data); + } + if (opt._option_method != (OptionDispatchMethod)nullptr) { + okflag = (*opt._option_method)(this, opt._option, arg, opt._option_data); + } + if (opt._bool_var != nullptr) { + (*opt._bool_var) = true; + } + + if (!okflag) { + show_usage(); + if (exit_on_complete) { + exit(1); + } + return ExitCode::EC_failure; + } + } + } + + flag = + getopt_long_only(argc, argv, short_options.c_str(), long_opts, nullptr); + } + + if (!handle_args(remaining_args)) { + show_usage(); + if (exit_on_complete) { + exit(1); + } + return ExitCode::EC_failure; + } + + if (!post_command_line()) { + show_usage(); + if (exit_on_complete) { + exit(1); + } + return ExitCode::EC_failure; + } + + return ExitCode::EC_not_exited; +} + +/** + * Returns the command that invoked this program, as a shell-friendly string, + * suitable for pasting into the comments of output files. + */ +string ProgramBase:: +get_exec_command() const { + string command; + + command = _program_name.get_basename_wo_extension(); + Args::const_iterator ai; + for (ai = _program_args.begin(); ai != _program_args.end(); ++ai) { + const string &arg = (*ai); + + // First, check to see if the string is shell-acceptable. + bool legal = true; + string::const_iterator si; + for (si = arg.begin(); legal && si != arg.end(); ++si) { + switch (*si) { + case ' ': + case '\n': + case '\t': + case '*': + case '?': + case '\\': + case '(': + case ')': + case '|': + case '&': + case '<': + case '>': + case '"': + case ';': + case '$': + legal = false; + } + } + + if (legal) { + command += " " + arg; + } else { + command += " '" + arg + "'"; + } + } + + return command; +} + + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool ProgramBase:: +handle_args(ProgramBase::Args &args) { + if (!args.empty()) { + nout << "Unexpected arguments on command line:\n"; + Args::const_iterator ai; + for (ai = args.begin(); ai != args.end(); ++ai) { + nout << (*ai) << " "; + } + nout << "\r"; + return false; + } + + return true; +} + +/** + * This is called after the command line has been completely processed, and it + * gives the program a chance to do some last-minute processing and validation + * of the options and arguments. It should return true if everything is fine, + * false if there is an error. + */ +bool ProgramBase:: +post_command_line() { + return true; +} + +/** + * Sets a brief synopsis of the program's function. This is currently only + * used for generating the synopsis of the program's man page. + * + * This should be of the format: "perform operation foo on bar files" + */ +void ProgramBase:: +set_program_brief(const string &brief) { + _brief = brief; +} + +/** + * Sets the description of the program that will be reported by show_usage(). + * The description should be one long string of text. Embedded newline + * characters are interpreted as paragraph breaks and printed as blank lines. + */ +void ProgramBase:: +set_program_description(const string &description) { + _description = description; +} + +/** + * Removes all of the runlines that were previously added, presumably before + * adding some new ones. + */ +void ProgramBase:: +clear_runlines() { + _runlines.clear(); +} + +/** + * Adds an additional line to the list of lines that will be displayed to + * describe briefly how the program is to be run. Each line should be + * something like "[opts] arg1 arg2", that is, it does *not* include the name + * of the program, but it includes everything that should be printed after the + * name of the program. + * + * Normally there is only one runline for a given program, but it is possible + * to define more than one. + */ +void ProgramBase:: +add_runline(const string &runline) { + _runlines.push_back(runline); +} + +/** + * Removes all of the options that were previously added, presumably before + * adding some new ones. Normally you wouldn't want to do this unless you + * want to completely replace all of the options defined by base classes. + */ +void ProgramBase:: +clear_options() { + _options_by_name.clear(); +} + +/** + * Adds (or redefines) a command line option. When parse_command_line() is + * executed it will look for these options (followed by a hyphen) on the + * command line; when a particular option is found it will call the indicated + * option_function, supplying the provided option_data. This allows the user + * to define a function that does some special behavior for any given option, + * or to use any of a number of generic pre-defined functions to fill in data + * for each option. + * + * Each option may or may not take a parameter. If parm_name is nonempty, it + * is assumed that the option does take a parameter (and parm_name contains + * the name that will be printed by show_options()). This parameter will be + * supplied as the second parameter to the dispatch function. If parm_name is + * empty, it is assumed that the option does not take a parameter. There is + * no provision for optional parameters. + * + * The options are listed first in order by their index_group number, and then + * in the order that add_option() was called. This provides a mechanism for + * listing the options defined in derived classes before those of the base + * classes. + */ +void ProgramBase:: +add_option(const string &option, const string &parm_name, + int index_group, const string &description, + OptionDispatchFunction option_function, + bool *bool_var, void *option_data) { + Option opt; + opt._option = option; + opt._parm_name = parm_name; + opt._index_group = index_group; + opt._sequence = ++_next_sequence; + opt._description = description; + opt._option_function = option_function; + opt._option_method = (OptionDispatchMethod)nullptr; + opt._bool_var = bool_var; + opt._option_data = option_data; + + _options_by_name[option] = opt; + _sorted_options = false; + + if (bool_var != nullptr) { + (*bool_var) = false; + } +} + +/** + * This is another variant on add_option(), above, except that it receives a + * pointer to a "method", which is really just another static (or global) + * function, whose first parameter is a ProgramBase *. + * + * We can't easily add a variant that accepts a real method, because the C++ + * syntax for methods requires us to know exactly what class object the method + * is defined for, and we want to support adding pointers for methods that are + * defined in other classes. So we have this hacky thing, which requires the + * "method" to be declared static, and receive its this pointer explicitly, as + * the first argument. + */ +void ProgramBase:: +add_option(const string &option, const string &parm_name, + int index_group, const string &description, + OptionDispatchMethod option_method, + bool *bool_var, void *option_data) { + Option opt; + opt._option = option; + opt._parm_name = parm_name; + opt._index_group = index_group; + opt._sequence = ++_next_sequence; + opt._description = description; + opt._option_function = (OptionDispatchFunction)nullptr; + opt._option_method = option_method; + opt._bool_var = bool_var; + opt._option_data = option_data; + + _options_by_name[option] = opt; + _sorted_options = false; + + if (bool_var != nullptr) { + (*bool_var) = false; + } +} + +/** + * Changes the description associated with a previously-defined option. + * Returns true if the option was changed, false if it hadn't been defined. + */ +bool ProgramBase:: +redescribe_option(const string &option, const string &description) { + OptionsByName::iterator oi = _options_by_name.find(option); + if (oi == _options_by_name.end()) { + return false; + } + (*oi).second._description = description; + return true; +} + +/** + * Removes a previously-defined option. Returns true if the option was + * removed, false if it hadn't existed. + */ +bool ProgramBase:: +remove_option(const string &option) { + OptionsByName::iterator oi = _options_by_name.find(option); + if (oi == _options_by_name.end()) { + return false; + } + _options_by_name.erase(oi); + _sorted_options = false; + return true; +} + +/** + * Adds -pr etc. as valid options for this program. These are appropriate + * for a model converter or model reader type program, and specify how to + * locate possibly-invalid pathnames in the source model file. + */ +void ProgramBase:: +add_path_replace_options() { + add_option + ("pr", "path_replace", 40, + "Sometimes references to other files (textures, external references) " + "are stored with a full path that is appropriate for some other system, " + "but does not exist here. This option may be used to specify how " + "those invalid paths map to correct paths. Generally, this is of " + "the form 'orig_prefix=replacement_prefix', which indicates a " + "particular initial sequence of characters that should be replaced " + "with a new sequence; e.g. '/c/home/models=/beta/fish'. " + "If the replacement prefix does not begin with a slash, the file " + "will then be searched for along the search path specified by -pp. " + "You may use standard filename matching characters ('*', '?', etc.) in " + "the original prefix, and '**' as a component by itself stands for " + "any number of components.\n\n" + + "This option may be repeated as necessary; each file will be tried " + "against each specified method, in the order in which they appear in " + "the command line, until the file is found. If the file is not found, " + "the last matching prefix is used anyway.", + &ProgramBase::dispatch_path_replace, nullptr, _path_replace.p()); + + add_option + ("pp", "dirname", 40, + "Adds the indicated directory name to the list of directories to " + "search for filenames referenced by the source file. This is used " + "only for relative paths, or for paths that are made relative by a " + "-pr replacement string that doesn't begin with a leading slash. " + "The model-path is always implicitly searched anyway.", + &ProgramBase::dispatch_search_path, nullptr, &(_path_replace->_path)); +} + +/** + * Adds -ps etc. as valid options for this program. These are appropriate + * for a model converter type program, and specify how to represent filenames + * in the output file. + */ +void ProgramBase:: +add_path_store_options() { + // If a program has path store options at all, the default path store is + // relative. + _path_replace->_path_store = PS_relative; + + add_option + ("ps", "path_store", 40, + "Specifies the way an externally referenced file is to be " + "represented in the resulting output file. This " + "assumes the named filename actually exists; " + "see -pr to indicate how to deal with external " + "references that have bad pathnames. " + "This option will not help you to find a missing file, but simply " + "controls how filenames are represented in the output.\n\n" + + "The option may be one of: rel, abs, rel_abs, strip, or keep. If " + "either rel or rel_abs is specified, the files are made relative to " + "the directory specified by -pd. The default is rel.", + &ProgramBase::dispatch_path_store, &_got_path_store, + &(_path_replace->_path_store)); + + add_option + ("pd", "path_directory", 40, + "Specifies the name of a directory to make paths relative to, if " + "'-ps rel' or '-ps rel_abs' is specified. If this is omitted, the " + "directory name is taken from the name of the output file.", + &ProgramBase::dispatch_filename, &_got_path_directory, + &(_path_replace->_path_directory)); + + add_option + ("pc", "target_directory", 40, + "Copies textures and other dependent files into the indicated " + "directory. If a relative pathname is specified, it is relative " + "to the directory specified with -pd, above.", + &ProgramBase::dispatch_filename, &(_path_replace->_copy_files), + &(_path_replace->_copy_into_directory)); +} + +/** + * Standard dispatch function for an option that takes no parameters, and does + * nothing special. Typically this would be used for a boolean flag, whose + * presence means something and whose absence means something else. Use the + * bool_var parameter to add_option() to determine whether the option appears + * on the command line or not. + */ +bool ProgramBase:: +dispatch_none(const string &, const string &, void *) { + return true; +} + +/** + * Standard dispatch function for an option that takes no parameters, and when + * it is present sets a bool variable to the 'true' value. This is another + * way to handle a boolean flag. See also dispatch_none() and + * dispatch_false(). + * + * The data pointer is to a bool variable. + */ +bool ProgramBase:: +dispatch_true(const string &, const string &, void *var) { + bool *bp = (bool *)var; + (*bp) = true; + return true; +} + +/** + * Standard dispatch function for an option that takes no parameters, and when + * it is present sets a bool variable to the 'false' value. This is another + * way to handle a boolean flag. See also dispatch_none() and + * dispatch_true(). + * + * The data pointer is to a bool variable. + */ +bool ProgramBase:: +dispatch_false(const string &, const string &, void *var) { + bool *bp = (bool *)var; + (*bp) = false; + return true; +} + +/** + * Standard dispatch function for an option that takes no parameters, but + * whose presence on the command line increments an integer counter for each + * time it appears. -v is often an option that works this way. The data + * pointer is to an int counter variable. + */ +bool ProgramBase:: +dispatch_count(const string &, const string &, void *var) { + int *ip = (int *)var; + (*ip)++; + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as an integer. The data pointer is to an int variable. + */ +bool ProgramBase:: +dispatch_int(const string &opt, const string &arg, void *var) { + int *ip = (int *)var; + + if (!string_to_int(arg, *ip)) { + nout << "Invalid integer parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a pair of integer + * parameters. The data pointer is to an array of two integers. + */ +bool ProgramBase:: +dispatch_int_pair(const string &opt, const string &arg, void *var) { + int *ip = (int *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + if (words.size() == 2) { + okflag = + string_to_int(words[0], ip[0]) && + string_to_int(words[1], ip[1]); + } + + if (!okflag) { + nout << "-" << opt + << " requires a pair of integers separated by a comma.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a quad of integer + * parameters. The data pointer is to an array of four integers. + */ +bool ProgramBase:: +dispatch_int_quad(const string &opt, const string &arg, void *var) { + int *ip = (int *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + if (words.size() == 4) { + okflag = + string_to_int(words[0], ip[0]) && + string_to_int(words[1], ip[1]) && + string_to_int(words[1], ip[2]) && + string_to_int(words[1], ip[3]); + } + + if (!okflag) { + nout << "-" << opt + << " requires a quad of integers separated by a comma.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a double. The data pointer is to an double variable. + */ +bool ProgramBase:: +dispatch_double(const string &opt, const string &arg, void *var) { + double *ip = (double *)var; + + if (!string_to_double(arg, *ip)) { + nout << "Invalid numeric parameter for -" << opt << ": " + << arg << "\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a pair of double + * parameters. The data pointer is to an array of two doubles. + */ +bool ProgramBase:: +dispatch_double_pair(const string &opt, const string &arg, void *var) { + double *ip = (double *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + if (words.size() == 2) { + okflag = + string_to_double(words[0], ip[0]) && + string_to_double(words[1], ip[1]); + } + + if (!okflag) { + nout << "-" << opt + << " requires a pair of numbers separated by a comma.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a triple of double + * parameters. The data pointer is to an array of three doubles. + */ +bool ProgramBase:: +dispatch_double_triple(const string &opt, const string &arg, void *var) { + double *ip = (double *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + if (words.size() == 3) { + okflag = + string_to_double(words[0], ip[0]) && + string_to_double(words[1], ip[1]) && + string_to_double(words[2], ip[2]); + } + + if (!okflag) { + nout << "-" << opt + << " requires three numbers separated by commas.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a quad of double + * parameters. The data pointer is to an array of four doubles. + */ +bool ProgramBase:: +dispatch_double_quad(const string &opt, const string &arg, void *var) { + double *ip = (double *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + if (words.size() == 4) { + okflag = + string_to_double(words[0], ip[0]) && + string_to_double(words[1], ip[1]) && + string_to_double(words[2], ip[2]) && + string_to_double(words[3], ip[3]); + } + + if (!okflag) { + nout << "-" << opt + << " requires four numbers separated by commas.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes a color, as l or l,a or + * r,g,b or r,g,b,a. The data pointer is to an array of four floats, e.g. a + * LColor. + */ +bool ProgramBase:: +dispatch_color(const string &opt, const string &arg, void *var) { + PN_stdfloat *ip = (PN_stdfloat *)var; + + vector_string words; + tokenize(arg, words, ","); + + bool okflag = false; + switch (words.size()) { + case 4: + okflag = + string_to_stdfloat(words[0], ip[0]) && + string_to_stdfloat(words[1], ip[1]) && + string_to_stdfloat(words[2], ip[2]) && + string_to_stdfloat(words[3], ip[3]); + break; + + case 3: + okflag = + string_to_stdfloat(words[0], ip[0]) && + string_to_stdfloat(words[1], ip[1]) && + string_to_stdfloat(words[2], ip[2]); + ip[3] = 1.0; + break; + + case 2: + okflag = + string_to_stdfloat(words[0], ip[0]) && + string_to_stdfloat(words[1], ip[3]); + ip[1] = ip[0]; + ip[2] = ip[0]; + break; + + case 1: + okflag = + string_to_stdfloat(words[0], ip[0]); + ip[1] = ip[0]; + ip[2] = ip[0]; + ip[3] = 1.0; + break; + } + + if (!okflag) { + nout << "-" << opt + << " requires one through four numbers separated by commas.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a string. The data pointer is to a string variable. + */ +bool ProgramBase:: +dispatch_string(const string &, const string &arg, void *var) { + string *ip = (string *)var; + (*ip) = arg; + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a string. This is different from dispatch_string in + * that the parameter may be repeated multiple times, and each time the string + * value is appended to a vector. + * + * The data pointer is to a vector_string variable. + */ +bool ProgramBase:: +dispatch_vector_string(const string &, const string &arg, void *var) { + vector_string *ip = (vector_string *)var; + (*ip).push_back(arg); + + return true; +} + +/** + * Similar to dispatch_vector_string, but a comma is allowed to separate + * multiple tokens in one argument, without having to repeat the argument for + * each token. + * + * The data pointer is to a vector_string variable. + */ +bool ProgramBase:: +dispatch_vector_string_comma(const string &, const string &arg, void *var) { + vector_string *ip = (vector_string *)var; + + vector_string words; + tokenize(arg, words, ","); + + vector_string::const_iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + (*ip).push_back(*wi); + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a filename. The data pointer is to a Filename + * variable. + */ +bool ProgramBase:: +dispatch_filename(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt << " requires a filename parameter.\n"; + return false; + } + + Filename *ip = (Filename *)var; + (*ip) = Filename::from_os_specific(arg); + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a single directory name to add to a search path. The + * data pointer is to a DSearchPath variable. This kind of option may appear + * multiple times on the command line; each time, the new directory is + * appended. + */ +bool ProgramBase:: +dispatch_search_path(const string &opt, const string &arg, void *var) { + if (arg.empty()) { + nout << "-" << opt << " requires a search path parameter.\n"; + return false; + } + + DSearchPath *ip = (DSearchPath *)var; + ip->append_directory(Filename::from_os_specific(arg)); + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a coordinate system string. The data pointer is to a + * CoordinateSystem variable. + */ +bool ProgramBase:: +dispatch_coordinate_system(const string &opt, const string &arg, void *var) { + CoordinateSystem *ip = (CoordinateSystem *)var; + (*ip) = parse_coordinate_system_string(arg); + + if ((*ip) == CS_invalid) { + nout << "Invalid coordinate system for -" << opt << ": " << arg << "\n" + << "Valid coordinate system strings are any of 'y-up', 'z-up', " + "'y-up-left', or 'z-up-left'.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a unit of distance measurement. The data pointer is + * to a DistanceUnit variable. + */ +bool ProgramBase:: +dispatch_units(const string &opt, const string &arg, void *var) { + DistanceUnit *ip = (DistanceUnit *)var; + (*ip) = string_distance_unit(arg); + + if ((*ip) == DU_invalid) { + nout << "Invalid units for -" << opt << ": " << arg << "\n" + << "Valid units are mm, cm, m, km, yd, ft, in, nmi, and mi.\n"; + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to indicate an image file type, like rgb, bmp, jpg, etc. The data pointer + * is to a PNMFileType pointer. + */ +bool ProgramBase:: +dispatch_image_type(const string &opt, const string &arg, void *var) { + PNMFileType **ip = (PNMFileType **)var; + + PNMFileTypeRegistry *reg = PNMFileTypeRegistry::get_global_ptr(); + + (*ip) = reg->get_type_from_extension(arg); + + if ((*ip) == nullptr) { + nout << "Invalid image type for -" << opt << ": " << arg << "\n" + << "The following image types are known:\n"; + reg->write(nout, 2); + return false; + } + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a single component of a path replace request. The + * data pointer is to a PathReplace variable. + */ +bool ProgramBase:: +dispatch_path_replace(const string &opt, const string &arg, void *var) { + PathReplace *ip = (PathReplace *)var; + size_t equals = arg.find('='); + if (equals == string::npos) { + nout << "Invalid path replacement string for -" << opt << ": " << arg << "\n" + << "String should be of the form 'old-prefix=new-prefix'.\n"; + return false; + } + ip->add_pattern(arg.substr(0, equals), arg.substr(equals + 1)); + + return true; +} + +/** + * Standard dispatch function for an option that takes one parameter, which is + * to be interpreted as a path store string. The data pointer is to a + * PathStore variable. + */ +bool ProgramBase:: +dispatch_path_store(const string &opt, const string &arg, void *var) { + PathStore *ip = (PathStore *)var; + (*ip) = string_path_store(arg); + + if ((*ip) == PS_invalid) { + nout << "Invalid path store for -" << opt << ": " << arg << "\n" + << "Valid path store strings are any of 'rel', 'abs', " + << "'rel_abs', 'strip', or 'keep'.\n"; + return false; + } + + return true; +} + +/** + * Called when the user enters '-h', this describes how to use the program and + * then exits. + */ +bool ProgramBase:: +handle_help_option(const string &, const string &, void *data) { + ProgramBase *me = (ProgramBase *)data; + me->show_description(); + me->show_usage(); + me->show_options(); + exit(0); + + return false; +} + + +/** + * Word-wraps the indicated text to the indicated output stream. The first + * line is prefixed with the indicated prefix, then tabbed over to + * indent_width where the text actually begins. A newline is inserted at or + * before column line_width. Each subsequent line begins with indent_width + * spaces. + * + * An embedded newline character ('\n') forces a line break, while an embedded + * carriage-return character ('\r'), or two or more consecutive newlines, + * marks a paragraph break, which is usually printed as a blank line. + * Redundant newline and carriage-return characters are generally ignored. + * + * The flag last_newline should be initialized to false for the first call to + * format_text, and then preserved for future calls; it tracks the state of + * trailing newline characters between calls so we can correctly identify + * doubled newlines. + */ +void ProgramBase:: +format_text(std::ostream &out, bool &last_newline, + const string &prefix, int indent_width, + const string &text, int line_width) { + indent_width = min(indent_width, line_width - 20); + int indent_amount = indent_width; + bool initial_break = false; + + if (!prefix.empty()) { + out << prefix; + indent_amount = indent_width - prefix.length(); + if ((int)prefix.length() + 1 > indent_width) { + out << "\n"; + initial_break = true; + indent_amount = indent_width; + } + } + + size_t p = 0; + + // Skip any initial whitespace and newlines. + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\r' || + (p > 0 && text[p] == '\n' && text[p - 1] == '\n') || + (p == 0 && text[p] == '\n' && last_newline)) { + if (!initial_break) { + // Here's an initial paragraph break, however. + out << "\n"; + initial_break = true; + } + indent_amount = indent_width; + + } else if (text[p] == '\n') { + // Largely ignore an initial newline. + indent_amount = indent_width; + + } else if (text[p] == ' ') { + // Do count up leading spaces. + indent_amount++; + } + p++; + } + + last_newline = (!text.empty() && text[text.length() - 1] == '\n'); + + while (p < text.length()) { + // Look for the paragraph or line break--the next newline character, if + // any. + size_t par = text.find_first_of("\n\r", p); + bool is_paragraph_break = false; + if (par == string::npos) { + par = text.length(); + /* + This shouldn't be necessary. + } else { + is_paragraph_break = (text[par] == '\r'); + */ + } + + indent(out, indent_amount); + + size_t eol = p + (line_width - indent_width); + if (eol >= par) { + // The rest of the paragraph fits completely on the line. + eol = par; + + } else { + // The paragraph doesn't fit completely on the line. Determine the best + // place to break the line. Look for the last space before the ideal + // eol. + size_t min_eol = max((int)p, (int)eol - 25); + size_t q = eol; + while (q > min_eol && !isspace(text[q])) { + q--; + } + // Now roll back to the last non-space before this one. + while (q > min_eol && isspace(text[q])) { + q--; + } + + if (q != min_eol) { + // Here's a good place to stop! + eol = q + 1; + + } else { + // The line cannot be broken cleanly. Just let it keep going; don't + // try to wrap it. + eol = par; + } + } + out << text.substr(p, eol - p) << "\n"; + p = eol; + + // Skip additional whitespace between the lines. + while (p < text.length() && isspace(text[p])) { + if (text[p] == '\r' || + (p > 0 && text[p] == '\n' && text[p - 1] == '\n')) { + is_paragraph_break = true; + } + p++; + } + + if (eol == par && is_paragraph_break) { + // Print the paragraph break as a blank line. + out << "\n"; + if (p >= text.length()) { + // If we end on a paragraph break, don't try to insert a new one in + // the next pass. + last_newline = false; + } + } + + indent_amount = indent_width; + } +} + + +/** + * Puts all the options in order by index number (e.g. in the order they were + * added, within index_groups), for output by show_options(). + */ +void ProgramBase:: +sort_options() { + if (!_sorted_options) { + _options_by_index.clear(); + + OptionsByName::const_iterator oi; + for (oi = _options_by_name.begin(); oi != _options_by_name.end(); ++oi) { + _options_by_index.push_back(&(*oi).second); + } + + sort(_options_by_index.begin(), _options_by_index.end(), + SortOptionsByIndex()); + _sorted_options = true; + } +} + +/** + * Attempts to determine the ideal terminal width for formatting output. + */ +void ProgramBase:: +get_terminal_width() { + if (!_got_terminal_width) { + _got_terminal_width = true; + _got_option_indent = false; + +#ifdef IOCTL_TERMINAL_WIDTH + if (use_terminal_width) { + struct winsize size; + int result = ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&size); + if (result < 0 || size.ws_col < 10) { + // Couldn't determine the width for some reason. Instead of + // complaining, just punt. + _terminal_width = default_terminal_width; + } else { + + // Subtract 10% for the comfort margin at the edge. + _terminal_width = size.ws_col - min(8, (int)(size.ws_col * 0.1)); + } + return; + } +#endif // IOCTL_TERMINAL_WIDTH + + _terminal_width = default_terminal_width; + } +} diff --git a/pandatool/src/progbase/programBase.h b/pandatool/src/progbase/programBase.h new file mode 100644 index 00000000..a245e999 --- /dev/null +++ b/pandatool/src/progbase/programBase.h @@ -0,0 +1,171 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file programBase.h + * @author drose + * @date 2000-02-13 + */ + +#ifndef PROGRAMBASE_H +#define PROGRAMBASE_H + +#include "pandatoolbase.h" + +#include "distanceUnit.h" +#include "pathReplace.h" +#include "pathStore.h" +#include "filename.h" +#include "pointerTo.h" +#include "vector_string.h" +#include "pvector.h" +#include "pdeque.h" +#include "pmap.h" + +/** + * This is intended to be the base class for most general-purpose utility + * programs in the PANDATOOL tree. It automatically handles things like + * command-line arguments in a portable way. + */ +class ProgramBase { +public: + enum ExitCode { + EC_not_exited = -1, + EC_clean_exit = 0, + EC_failure = 1 + }; + + ProgramBase(const std::string &name = std::string()); + virtual ~ProgramBase(); + + void show_description(); + void show_usage(); + void show_options(); + + INLINE void show_text(const std::string &text); + void show_text(const std::string &prefix, int indent_width, std::string text); + + void write_man_page(std::ostream &out); + + virtual ExitCode parse_command_line(int argc, char **argv, bool exit_on_complete = true); + + std::string get_exec_command() const; + + typedef pdeque Args; + Filename _program_name; + Args _program_args; + +protected: + typedef bool (*OptionDispatchFunction)(const std::string &opt, const std::string &parm, void *data); + typedef bool (*OptionDispatchMethod)(ProgramBase *self, const std::string &opt, const std::string &parm, void *data); + + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + void set_program_brief(const std::string &brief); + void set_program_description(const std::string &description); + void clear_runlines(); + void add_runline(const std::string &runline); + void clear_options(); + void add_option(const std::string &option, const std::string &parm_name, + int index_group, const std::string &description, + OptionDispatchFunction option_function, + bool *bool_var = nullptr, + void *option_data = nullptr); + void add_option(const std::string &option, const std::string &parm_name, + int index_group, const std::string &description, + OptionDispatchMethod option_method, + bool *bool_var = nullptr, + void *option_data = nullptr); + bool redescribe_option(const std::string &option, const std::string &description); + bool remove_option(const std::string &option); + + void add_path_replace_options(); + void add_path_store_options(); + + static bool dispatch_none(const std::string &opt, const std::string &arg, void *); + static bool dispatch_true(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_false(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_count(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_int(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_int_pair(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_int_quad(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_double(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_double_pair(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_double_triple(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_double_quad(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_color(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_string(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_vector_string(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_vector_string_comma(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_filename(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_search_path(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_coordinate_system(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_units(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_image_type(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_path_replace(const std::string &opt, const std::string &arg, void *var); + static bool dispatch_path_store(const std::string &opt, const std::string &arg, void *var); + + static bool handle_help_option(const std::string &opt, const std::string &arg, void *); + + static void format_text(std::ostream &out, bool &last_newline, + const std::string &prefix, int indent_width, + const std::string &text, int line_width); + + PT(PathReplace) _path_replace; + bool _got_path_store; + bool _got_path_directory; + + +private: + void sort_options(); + void get_terminal_width(); + + class Option { + public: + std::string _option; + std::string _parm_name; + int _index_group; + int _sequence; + std::string _description; + OptionDispatchFunction _option_function; + OptionDispatchMethod _option_method; + bool *_bool_var; + void *_option_data; + }; + + class SortOptionsByIndex { + public: + bool operator () (const Option *a, const Option *b) const; + }; + + std::string _name; + std::string _brief; + std::string _description; + typedef vector_string Runlines; + Runlines _runlines; + + typedef pmap OptionsByName; + typedef pvector OptionsByIndex; + OptionsByName _options_by_name; + OptionsByIndex _options_by_index; + int _next_sequence; + bool _sorted_options; + + typedef pmap GotOptions; + GotOptions _got_options; + + bool _last_newline; + int _terminal_width; + bool _got_terminal_width; + int _option_indent; + bool _got_option_indent; +}; + +#include "programBase.I" + +#endif diff --git a/pandatool/src/progbase/test_prog.cxx b/pandatool/src/progbase/test_prog.cxx new file mode 100644 index 00000000..289b68c8 --- /dev/null +++ b/pandatool/src/progbase/test_prog.cxx @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file test_prog.cxx + * @author drose + * @date 2000-02-14 + */ + +#include "programBase.h" + +#include "pnotify.h" + +class TestProgram : public ProgramBase { +public: + TestProgram(); + + bool _bool_a; + int _count_b; + int _int_c; +}; + +TestProgram:: +TestProgram() { + set_program_brief("test program for ProgramBase class"); + set_program_description + ("This is a simple test program to verify the effectiveness of the " + "ProgramBase base class as a base class for simple programs. It " + "includes some simple options and some description strings that are " + "long enough to require word-wrapping.\r" + "Don't expect anything fancy, though."); + add_runline("[opts]"); + + add_option + ("bog", "", 90, + "This is test option 'bog'. It is a simple boolean toggle; if it appears " + "at all, it sets a boolean flag to indicate that. If it does not " + "appear, it leaves the boolean flag alone.\r" + "There's not a whole lot of point to this option, when you come down " + "to it.", + &TestProgram::dispatch_none, &_bool_a); + + add_option + ("b", "", 90, "Test option b", + &TestProgram::dispatch_count, nullptr, &_count_b); + _count_b = 0; + + add_option + ("c", "integer_parameter", 90, + "This is test option 'c'. It takes an integer parameter.", + &TestProgram::dispatch_int, nullptr, &_int_c); + _int_c = 0; +} + + +int main(int argc, char *argv[]) { + TestProgram t; + t.parse_command_line(argc, argv); + + nout << "Executed successfully.\n" + << " _bool_a = " << t._bool_a << "\n" + << " _count_b = " << t._count_b << "\n" + << " _int_c = " << t._int_c << "\n"; + return 0; +} diff --git a/pandatool/src/progbase/withOutputFile.I b/pandatool/src/progbase/withOutputFile.I new file mode 100644 index 00000000..1daa8487 --- /dev/null +++ b/pandatool/src/progbase/withOutputFile.I @@ -0,0 +1,21 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file withOutputFile.I + * @author drose + * @date 2003-10-01 + */ + +/** + * Changes the flag specifying whether the output file is to be opened in + * binary mode or not. + */ +INLINE void WithOutputFile:: +set_binary_output(bool binary_output) { + _binary_output = binary_output; +} diff --git a/pandatool/src/progbase/withOutputFile.cxx b/pandatool/src/progbase/withOutputFile.cxx new file mode 100644 index 00000000..04e5005f --- /dev/null +++ b/pandatool/src/progbase/withOutputFile.cxx @@ -0,0 +1,200 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file withOutputFile.cxx + * @author drose + * @date 2001-04-11 + */ + +#include "withOutputFile.h" +#include "executionEnvironment.h" +#include "zStream.h" + +#include "pnotify.h" + +/** + * + */ +WithOutputFile:: +WithOutputFile(bool allow_last_param, bool allow_stdout, + bool binary_output) { + _allow_last_param = allow_last_param; + _allow_stdout = allow_stdout; + _binary_output = binary_output; + _got_output_filename = false; + _output_ptr = nullptr; + _owns_output_ptr = false; +} + +/** + * + */ +WithOutputFile:: +~WithOutputFile() { + if (_owns_output_ptr) { + delete _output_ptr; + _owns_output_ptr = false; + } +} + +/** + * Returns an output stream that corresponds to the user's intended egg file + * output--either stdout, or the named output file. + */ +std::ostream &WithOutputFile:: +get_output() { + if (_output_ptr == nullptr) { + if (!_got_output_filename) { + // No filename given; use standard output. + if (!_allow_stdout) { + nout << "No output filename specified.\n"; + exit(1); + } + _output_ptr = &std::cout; + _owns_output_ptr = false; + + } else { + // Attempt to open the named file. + unlink(_output_filename.c_str()); + _output_filename.make_dir(); + + bool pz_file = false; +#ifdef HAVE_ZLIB + if (_output_filename.get_extension() == "pz") { + // The filename ends in .pz, which means to automatically compress the + // file that we write. + pz_file = true; + } +#endif // HAVE_ZLIB + + if (_binary_output || pz_file) { + _output_filename.set_binary(); + } else { + _output_filename.set_text(); + } + + _output_stream.clear(); + if (!_output_filename.open_write(_output_stream)) { + nout << "Unable to write to " << _output_filename << "\n"; + exit(1); + } + nout << "Writing " << _output_filename << "\n"; + _output_ptr = &_output_stream; + _owns_output_ptr = false; + +#ifdef HAVE_ZLIB + if (pz_file) { + _output_ptr = new OCompressStream(_output_ptr, _owns_output_ptr); + _owns_output_ptr = true; + } +#endif // HAVE_ZLIB + } + } + return *_output_ptr; +} + +/** + * Closes the output stream previously opened by get_output(). A subsequent + * call to get_output() will open a new stream. + */ +void WithOutputFile:: +close_output() { + if (_owns_output_ptr) { + delete _output_ptr; + _owns_output_ptr = false; + } + _output_ptr = nullptr; + _output_stream.close(); +} + + + +/** + * Returns true if the user specified an output filename, false otherwise + * (e.g. the output file is implicitly stdout). + */ +bool WithOutputFile:: +has_output_filename() const { + return _got_output_filename; +} + +/** + * If has_output_filename() returns true, this is the filename that the user + * specified. Otherwise, it returns the empty string. + */ +Filename WithOutputFile:: +get_output_filename() const { + if (_got_output_filename) { + return _output_filename; + } + return Filename(); +} + +/** + * Checks if the last filename on the argument list is a file with the + * expected extension (if _allow_last_param was set true), and removes it from + * the argument list if it is. Returns true if the arguments are good, false + * if something is invalid. + * + * minimum_args is the number of arguments we know must be input parameters + * and therefore cannot be interpreted as output filenames. + */ +bool WithOutputFile:: +check_last_arg(ProgramBase::Args &args, int minimum_args) { + if (_allow_last_param && !_got_output_filename && + (int)args.size() > minimum_args) { + Filename filename = Filename::from_os_specific(args.back()); + + if (!_preferred_extension.empty() && + ("." + filename.get_extension()) != _preferred_extension) { + // This argument must not be an output filename. + if (!_allow_stdout) { + nout << "Output filename " << filename + << " does not end in " << _preferred_extension + << ". If this is really what you intended, " + "use the -o output_file syntax.\n"; + return false; + } + + } else { + // This argument appears to be an output filename. + _got_output_filename = true; + _output_filename = filename; + args.pop_back(); + + if (!verify_output_file_safe()) { + return false; + } + } + } + + return true; +} + +/** + * This is called when the output file is given as the last parameter on the + * command line. Since this is a fairly dangerous way to specify the output + * file (it's easy to accidentally overwrite an input file this way), the + * convention is to disallow this syntax if the output file already exists. + * + * This function will test if the output file exists, and issue a warning + * message if it does, returning false. If all is well, it will return true. + */ +bool WithOutputFile:: +verify_output_file_safe() const { + nassertr(_got_output_filename, false); + + if (_output_filename.exists()) { + nout << "The output filename " << _output_filename << " already exists. " + "If you wish to overwrite it, you must use the -o option to specify " + "the output filename, instead of simply specifying it as the last " + "parameter.\n"; + return false; + } + return true; +} diff --git a/pandatool/src/progbase/withOutputFile.h b/pandatool/src/progbase/withOutputFile.h new file mode 100644 index 00000000..385114c0 --- /dev/null +++ b/pandatool/src/progbase/withOutputFile.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file withOutputFile.h + * @author drose + * @date 2001-04-11 + */ + +#ifndef WITHOUTPUTFILE_H +#define WITHOUTPUTFILE_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "filename.h" + +/** + * This is the bare functionality (intended to be inherited from along with + * ProgramBase or some derivative) for a program that might generate an output + * file. + * + * This provides the has_output_filename() and get_output_filename() methods. + */ +class WithOutputFile { +public: + WithOutputFile(bool allow_last_param, bool allow_stdout, + bool binary_output); + virtual ~WithOutputFile(); + + std::ostream &get_output(); + void close_output(); + bool has_output_filename() const; + Filename get_output_filename() const; + +protected: + INLINE void set_binary_output(bool binary_output); + + bool check_last_arg(ProgramBase::Args &args, int minimum_args); + bool verify_output_file_safe() const; + +protected: + bool _allow_last_param; + bool _allow_stdout; + bool _binary_output; + std::string _preferred_extension; + bool _got_output_filename; + Filename _output_filename; + +private: + std::ofstream _output_stream; + std::ostream *_output_ptr; + bool _owns_output_ptr; +}; + +#include "withOutputFile.I" + +#endif diff --git a/pandatool/src/progbase/wordWrapStream.cxx b/pandatool/src/progbase/wordWrapStream.cxx new file mode 100644 index 00000000..cd069d1e --- /dev/null +++ b/pandatool/src/progbase/wordWrapStream.cxx @@ -0,0 +1,25 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file wordWrapStream.cxx + * @author drose + * @date 2000-06-28 + */ + +#include "wordWrapStream.h" + + +/** + * + */ +WordWrapStream:: +WordWrapStream(ProgramBase *program) : + std::ostream(&_lsb), + _lsb(this, program) +{ +} diff --git a/pandatool/src/progbase/wordWrapStream.h b/pandatool/src/progbase/wordWrapStream.h new file mode 100644 index 00000000..cfa7969d --- /dev/null +++ b/pandatool/src/progbase/wordWrapStream.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file wordWrapStream.h + * @author drose + * @date 2000-06-28 + */ + +#ifndef WORDWRAPSTREAM_H +#define WORDWRAPSTREAM_H + +#include "pandatoolbase.h" + +#include "wordWrapStreamBuf.h" + +/** + * A special ostream that formats all of its output through + * ProgramBase::show_text(). This allows the program to easily word-wrap its + * output messages to fit the terminal width. + * + * By convention (inherited from show_text), a newline written to the + * WordWrapStream indicates a paragraph break, and is generally printed as a + * blank line. To force a line break without a paragraph break, use '\r'. + */ +class WordWrapStream : public std::ostream { +public: + WordWrapStream(ProgramBase *program); + +private: + WordWrapStreamBuf _lsb; +}; + +#endif diff --git a/pandatool/src/progbase/wordWrapStreamBuf.I b/pandatool/src/progbase/wordWrapStreamBuf.I new file mode 100644 index 00000000..67a0f688 --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.I @@ -0,0 +1,26 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file wordWrapStreamBuf.I + * @author drose + * @date 2000-07-01 + */ + +/** + * An internal function called to update the internal state according to the + * current value of the Notify::literal flag, which might or might not be set + * of the ostream at any time. When the literal flag is true, we should not + * word-wrap, so toggling this flag means we need to flush the current buffer. + */ +INLINE void WordWrapStreamBuf:: +set_literal_mode(bool mode) { + if (mode != _literal_mode) { + flush_data(); + _literal_mode = mode; + } +} diff --git a/pandatool/src/progbase/wordWrapStreamBuf.cxx b/pandatool/src/progbase/wordWrapStreamBuf.cxx new file mode 100644 index 00000000..0e0c4abb --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.cxx @@ -0,0 +1,114 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file wordWrapStreamBuf.cxx + * @author drose + * @date 2000-06-28 + */ + +#include "wordWrapStreamBuf.h" +#include "wordWrapStream.h" +#include "programBase.h" + +#include "pnotify.h" + +/** + * + */ +WordWrapStreamBuf:: +WordWrapStreamBuf(WordWrapStream *owner, ProgramBase *program) : + _owner(owner), + _program(program) +{ + _literal_mode = false; +} + +/** + * + */ +WordWrapStreamBuf:: +~WordWrapStreamBuf() { + sync(); +} + +/** + * Called by the system ostream implementation when the buffer should be + * flushed to output (for instance, on destruction). + */ +int WordWrapStreamBuf:: +sync() { + std::streamsize n = pptr() - pbase(); + write_chars(pbase(), n); + + // Send all the data out now. + flush_data(); + + return 0; // EOF to indicate write full. +} + +/** + * Called by the system ostream implementation when its internal buffer is + * filled, plus one character. + */ +int WordWrapStreamBuf:: +overflow(int ch) { + std::streamsize n = pptr() - pbase(); + + if (n != 0 && sync() != 0) { + return EOF; + } + + if (ch != EOF) { + // Write one more character. + char c = ch; + write_chars(&c, 1); + } + + pbump(-n); // Reset pptr(). + return 0; +} + +/** + * An internal function called by sync() and overflow() to store one or more + * characters written to the stream into the memory buffer. + */ +void WordWrapStreamBuf:: +write_chars(const char *start, int length) { + if (length > 0) { + set_literal_mode((_owner->flags() & Notify::get_literal_flag()) != 0); + std::string new_data(start, length); + size_t newline = new_data.find_first_of("\n\r"); + size_t p = 0; + while (newline != std::string::npos) { + // The new data contains a newline; flush our data to that point. + _data += new_data.substr(p, newline - p + 1); + flush_data(); + p = newline + 1; + newline = new_data.find_first_of("\n\r", p); + } + + // Save the rest for the next write. + _data += new_data.substr(p); + } +} + +/** + * Writes the contents of _data to the actual output stream, either word- + * wrapped or not as appropriate, and empties the contents of _data. + */ +void WordWrapStreamBuf:: +flush_data() { + if (!_data.empty()) { + if (_literal_mode) { + std::cerr << _data; + } else { + _program->show_text(_data); + } + _data = ""; + } +} diff --git a/pandatool/src/progbase/wordWrapStreamBuf.h b/pandatool/src/progbase/wordWrapStreamBuf.h new file mode 100644 index 00000000..f1c54abf --- /dev/null +++ b/pandatool/src/progbase/wordWrapStreamBuf.h @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file wordWrapStreamBuf.h + * @author drose + * @date 2000-06-28 + */ + +#ifndef WORDWRAPSTREAMBUF_H +#define WORDWRAPSTREAMBUF_H + +#include "pandatoolbase.h" + +#include + +class ProgramBase; +class WordWrapStream; + +/** + * Used by WordWrapStream to implement an ostream that flushes its output to + * ProgramBase::show_text(). + */ +class WordWrapStreamBuf final : public std::streambuf { +public: + WordWrapStreamBuf(WordWrapStream *owner, ProgramBase *program); + virtual ~WordWrapStreamBuf(); + +protected: + virtual int overflow(int c); + virtual int sync(); + +private: + void write_chars(const char *start, int length); + INLINE void set_literal_mode(bool mode); + void flush_data(); + + std::string _data; + WordWrapStream *_owner; + ProgramBase *_program; + bool _literal_mode; +}; + +#include "wordWrapStreamBuf.I" + +#endif diff --git a/pandatool/src/pstatserver/CMakeLists.txt b/pandatool/src/pstatserver/CMakeLists.txt new file mode 100644 index 00000000..8587806e --- /dev/null +++ b/pandatool/src/pstatserver/CMakeLists.txt @@ -0,0 +1,46 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_NET) + return() +endif() + +set(P3PSTATSERVER_HEADERS + pStatClientData.h + pStatFlameGraph.h pStatFlameGraph.I + pStatGraph.h pStatGraph.I + pStatListener.h + pStatMonitor.h pStatMonitor.I + pStatPianoRoll.h pStatPianoRoll.I + pStatReader.h + pStatServer.h + pStatStripChart.h pStatStripChart.I + pStatThreadData.h pStatThreadData.I + pStatTimeline.h pStatTimeline.I + pStatView.h pStatView.I + pStatViewLevel.h pStatViewLevel.I +) + +set(P3PSTATSERVER_SOURCES + pStatClientData.cxx + pStatFlameGraph.cxx + pStatGraph.cxx + pStatListener.cxx + pStatMonitor.cxx + pStatPianoRoll.cxx + pStatReader.cxx + pStatServer.cxx + pStatStripChart.cxx + pStatThreadData.cxx + pStatTimeline.cxx + pStatView.cxx + pStatViewLevel.cxx +) + +composite_sources(p3pstatserver P3PSTATSERVER_SOURCES) +add_library(p3pstatserver STATIC ${P3PSTATSERVER_HEADERS} ${P3PSTATSERVER_SOURCES}) +target_link_libraries(p3pstatserver p3pandatoolbase panda) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/pstatserver/p3pstatserver_composite1.cxx b/pandatool/src/pstatserver/p3pstatserver_composite1.cxx new file mode 100644 index 00000000..a3f7c09d --- /dev/null +++ b/pandatool/src/pstatserver/p3pstatserver_composite1.cxx @@ -0,0 +1,13 @@ +#include "pStatClientData.cxx" +#include "pStatFlameGraph.cxx" +#include "pStatGraph.cxx" +#include "pStatListener.cxx" +#include "pStatMonitor.cxx" +#include "pStatPianoRoll.cxx" +#include "pStatReader.cxx" +#include "pStatServer.cxx" +#include "pStatStripChart.cxx" +#include "pStatThreadData.cxx" +#include "pStatTimeline.cxx" +#include "pStatView.cxx" +#include "pStatViewLevel.cxx" diff --git a/pandatool/src/pstatserver/pStatClientData.cxx b/pandatool/src/pstatserver/pStatClientData.cxx new file mode 100644 index 00000000..a3dc317d --- /dev/null +++ b/pandatool/src/pstatserver/pStatClientData.cxx @@ -0,0 +1,560 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatClientData.cxx + * @author drose + * @date 2000-07-11 + */ + +#include "pStatClientData.h" +#include "pStatFrameData.h" +#include "pStatReader.h" + +#include "pStatCollectorDef.h" + +using std::string; + +PStatCollectorDef PStatClientData::_null_collector(-1, "Unknown"); + +/** + * + */ +PStatClientData:: +PStatClientData(PStatReader *reader) : + _is_alive(true), + _is_dirty(false), + _reader(reader) +{ +} + +/** + * + */ +PStatClientData:: +~PStatClientData() { + for (Collector &collector : _collectors) { + delete collector._def; + } +} + +/** + * Clears the is_dirty() flag. + */ +void PStatClientData:: +clear_dirty() const { + _is_dirty = false; +} + +/** + * Returns true if the data was modified since the last time clear_dirty() was + * called. + */ +bool PStatClientData:: +is_dirty() const { + return _is_dirty; +} + +/** + * Returns true if the data is actively getting filled by a connected client, + * or false if the client has terminated. + */ +bool PStatClientData:: +is_alive() const { + return _is_alive; +} + +/** + * Closes the client connection if it is open. + */ +void PStatClientData:: +close() { + if (_is_alive && _reader != nullptr) { + _reader->close(); + _reader = nullptr; + _is_alive = false; + } +} + +/** + * Returns the timestamp (in seconds elapsed since connection) of the latest + * available frame. + */ +double PStatClientData:: +get_latest_time() const { + double time = 0.0; + for (const Thread &thread : _threads) { + if (thread._data != nullptr && !thread._data->is_empty()) { + time = std::max(time, thread._data->get_latest_time()); + } + } + + return time; +} + +/** + * Returns the total number of collectors the Data knows about. + */ +int PStatClientData:: +get_num_collectors() const { + return _collectors.size(); +} + +/** + * Returns true if the indicated collector has been defined by the client + * already, false otherwise. It is possible for the client to start streaming + * data before all of the collectors have been defined. + */ +bool PStatClientData:: +has_collector(int index) const { + return (index >= 0 && index < (int)_collectors.size() && + _collectors[index]._def != nullptr); +} + +/** + * Returns the index of the collector with the given full name, or -1 if no + * such collector has been defined by the client. + */ +int PStatClientData:: +find_collector(const std::string &fullname) const { + // Take the last bit, we can compare it more cheaply, only check the full + // name if the basename matches. + const char *colon = strrchr(fullname.c_str(), ':'); + std::string name(colon != nullptr ? colon + 1 : fullname.c_str()); + + for (int index = 0; index < get_num_collectors(); ++index) { + const PStatCollectorDef *def = _collectors[index]._def; + if (def != nullptr && def->_name == name && + get_collector_fullname(index) == fullname) { + return index; + } + } + + return -1; +} + +/** + * Returns the nth collector definition. + */ +const PStatCollectorDef &PStatClientData:: +get_collector_def(int index) const { + if (!has_collector(index)) { + return _null_collector; + } + return *_collectors[index]._def; +} + +/** + * Returns the name of the indicated collector. + */ +string PStatClientData:: +get_collector_name(int index) const { + if (!has_collector(index)) { + return "Unknown"; + } + const PStatCollectorDef *def = _collectors[index]._def; + return def->_name; +} + +/** + * Returns the "full name" of the indicated collector. This will be the + * concatenation of all of the collector's parents' names (except Frame) and + * the collector's own name. + */ +string PStatClientData:: +get_collector_fullname(int index) const { + if (!has_collector(index)) { + return "Unknown"; + } + + const PStatCollectorDef *def = _collectors[index]._def; + if (def->_parent_index == 0) { + return def->_name; + } else { + return get_collector_fullname(def->_parent_index) + ":" + def->_name; + } +} + +/** + * Indicates whether the given collector has level data (and consequently, + * whether it should appear on the Levels menu). + * + * The return value is true if anything changed, false otherwise. + */ +bool PStatClientData:: +set_collector_has_level(int index, int thread_index, bool flag) { + bool any_changed = false; + slot_collector(index); + nassertr(index >= 0 && index < (int)_collectors.size(), false); + + if (_collectors[index]._is_level.get_bit(thread_index) != flag) { + any_changed = true; + _collectors[index]._is_level.set_bit_to(thread_index, flag); + } + + // Turning this on for a given collector also implicitly turns all of its + // ancestors. + if (flag) { + PStatCollectorDef *def = _collectors[index]._def; + if (def != nullptr && def->_parent_index != 0) { + if (set_collector_has_level(def->_parent_index, thread_index, flag)) { + any_changed = true; + } + } + } + + if (any_changed) { + _is_dirty = true; + } + + return any_changed; +} + + +/** + * Returns whether the given collector has level data (and consequently, + * whether it should appear on the Levels menu). + */ +bool PStatClientData:: +get_collector_has_level(int index, int thread_index) const { + return (index >= 0 && index < (int)_collectors.size() && + _collectors[index]._is_level.get_bit(thread_index)); +} + +/** + * Returns the total number of collectors that are toplevel collectors. These + * are the collectors that are the children of "Frame", which is collector 0. + */ +int PStatClientData:: +get_num_toplevel_collectors() const { + return _toplevel_collectors.size(); +} + +/** + * Returns the collector index of the nth toplevel collector. Use this + * function to iterate through the n toplevel collectors indicated by + * get_num_toplevel_collectors(). + */ +int PStatClientData:: +get_toplevel_collector(int n) const { + nassertr(n >= 0 && n < (int)_toplevel_collectors.size(), 0); + return _toplevel_collectors[n]; +} + +/** + * Returns the total number of threads the Data knows about. + */ +int PStatClientData:: +get_num_threads() const { + return _threads.size(); +} + +/** + * Returns true if the indicated thread has been defined by the client + * already, false otherwise. It is possible for the client to start streaming + * data before all of the threads have been defined. + */ +bool PStatClientData:: +has_thread(int index) const { + return (index >= 0 && (size_t)index < _threads.size() && + !_threads[index]._name.empty()); +} + +/** + * Returns the index of the thread with the given name, or -1 if no such thread + * has yet been defined. + */ +int PStatClientData:: +find_thread(const std::string &name) const { + for (int index = 0; index < get_num_threads(); ++index) { + if (_threads[index]._name == name) { + return index; + } + } + + return -1; +} + +/** + * Returns the name of the indicated thread. + */ +string PStatClientData:: +get_thread_name(int index) const { + if (!has_thread(index)) { + return "Unknown"; + } + return _threads[index]._name; +} + +/** + * Returns the data associated with the indicated thread. This will create a + * thread definition if it does not already exist. + */ +const PStatThreadData *PStatClientData:: +get_thread_data(int index) const { + ((PStatClientData *)this)->define_thread(index); + nassertr(index >= 0 && (size_t)index < _threads.size(), nullptr); + return _threads[index]._data; +} + +/** + * Returns true if the given thread is still alive. + */ +bool PStatClientData:: +is_thread_alive(int index) const { + return (index >= 0 && (size_t)index < _threads.size() && _threads[index]._is_alive); +} + +/** + * Returns the number of Collectors between the indicated parent and the child + * Collector in the relationship graph. If child is the same as parent, + * returns zero. If child is an immediate child of parent, returns 1. If + * child is a grandchild of parent, returns 2, and so on. If child is not a + * descendant of parent at all, returns -1. + */ +int PStatClientData:: +get_child_distance(int parent, int child) const { + if (parent == child) { + return 0; + } + if (!has_collector(child) || child == 0) { + return -1; + } + int dist = get_child_distance(parent, get_collector_def(child)._parent_index); + if (dist == -1) { + return -1; + } else { + return dist + 1; + } +} + +/** + * Adds a new collector definition to the dataset. Presumably this is + * information just arrived from the client. + * + * The pointer will become owned by the PStatClientData object and will be + * freed on destruction. + */ +void PStatClientData:: +add_collector(PStatCollectorDef *def) { + slot_collector(def->_index); + nassertv(def->_index >= 0 && def->_index < (int)_collectors.size()); + + if (_collectors[def->_index]._def != nullptr) { + // Free the old definition, if any. + delete _collectors[def->_index]._def; + } + + _collectors[def->_index]._def = def; + update_toplevel_collectors(); + + // If we already had the _is_level flag set, it should be immediately + // applied to all ancestors. + const BitArray &is_level = _collectors[def->_index]._is_level; + int max_threads = is_level.get_num_bits(); + for (int thread_index = 0; thread_index < max_threads; ++thread_index) { + if (is_level.get_bit(thread_index)) { + set_collector_has_level(def->_parent_index, thread_index, true); + } + } + + _is_dirty = true; +} + +/** + * Adds a new thread definition to the dataset. Presumably this is + * information just arrived from the client. + */ +void PStatClientData:: +define_thread(int thread_index, const string &name, bool mark_alive) { + // A sanity check on the index number. + nassertv(thread_index < 1000); + + // Make sure we have enough slots allocated. + while ((int)_threads.size() <= thread_index) { + _threads.push_back(Thread()); + } + + if (mark_alive) { + _threads[thread_index]._is_alive = true; + } + + if (!name.empty()) { + _threads[thread_index]._name = name; + } + + if (_threads[thread_index]._data.is_null()) { + _threads[thread_index]._data = new PStatThreadData(this); + } + + _is_dirty = true; +} + +/** + * Indicates that the given thread has expired. Presumably this is information + * just arrived from the client. + */ +void PStatClientData:: +expire_thread(int thread_index) { + if (thread_index >= 0 && (size_t)thread_index < _threads.size()) { + _threads[thread_index]._is_alive = false; + } +} + +/** + * Removes the given thread data entirely. + */ +void PStatClientData:: +remove_thread(int thread_index) { + if (thread_index >= 0 && (size_t)thread_index < _threads.size()) { + _threads[thread_index]._name.clear(); + _threads[thread_index]._data.clear(); + _threads[thread_index]._is_alive = false; + } +} + +/** + * Makes room for and stores a new frame's worth of data associated with some + * particular thread (which may or may not have already been defined). + * + * The pointer will become owned by the PStatThreadData object and will be + * freed on destruction. + */ +void PStatClientData:: +record_new_frame(int thread_index, int frame_number, + PStatFrameData *frame_data) { + define_thread(thread_index); + nassertv(thread_index >= 0 && thread_index < (int)_threads.size()); + _threads[thread_index]._data->record_new_frame(frame_number, frame_data); + _is_dirty = true; +} + +/** + * Writes the client data in the form of a JSON output that can be loaded into + * Chrome's event tracer. + */ +void PStatClientData:: +write_json(std::ostream &out, int pid) const { + out << "[\n"; + + for (int thread_index = 0; thread_index < get_num_threads(); ++thread_index) { + const Thread &thread = _threads[thread_index]; + + if (thread_index == 0) { + out << "{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":" << pid + << ",\"tid\":0,\"args\":{\"name\":\"Main\"}}"; + } + else if (!thread._name.empty()) { + out + << ",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":" << pid + << ",\"tid\":" << thread_index << ",\"args\":{\"name\":\"" + << thread._name << "\"}}"; + } + if (thread._data == nullptr || thread._data->is_empty()) { + continue; + } + + int first_frame = thread._data->get_oldest_frame_number(); + int last_frame = thread._data->get_latest_frame_number(); + for (int frame_number = first_frame; frame_number <= last_frame; ++frame_number) { + const PStatFrameData &frame_data = thread._data->get_frame(frame_number); + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { + int collector_index = frame_data.get_time_collector(i); + out + << ",\n{\"name\":\"" << get_collector_fullname(collector_index) + << "\",\"ts\":" << (uint64_t)(frame_data.get_time(i) * 1000000) + << ",\"ph\":\"" << (frame_data.is_start(i) ? 'B' : 'E') << "\"" + << ",\"pid\":" << pid << ",\"tid\":" << thread_index << "}"; + } + } + } + + out << "\n]\n"; + out.flush(); +} + +/** + * Writes the client data to a datagram. + */ +void PStatClientData:: +write_datagram(Datagram &dg) const { + for (const Collector &collector : _collectors) { + PStatCollectorDef *def = collector._def; + if (def != nullptr && def->_index != -1) { + def->write_datagram(dg); + collector._is_level.write_datagram(nullptr, dg); + } + } + dg.add_int16(-1); + + int thread_index = 0; + for (const Thread &thread : _threads) { + if (thread._data != nullptr) { + dg.add_int16(thread_index); + dg.add_string(thread._name); + thread._data->write_datagram(dg); + } + ++thread_index; + } + dg.add_int16(-1); +} + +/** + * Restores the client data from a datagram. + */ +void PStatClientData:: +read_datagram(DatagramIterator &scan) { + while (scan.peek_int16() != -1) { + PStatCollectorDef *def = new PStatCollectorDef; + def->read_datagram(scan); + add_collector(def); + _collectors[def->_index]._is_level.read_datagram(scan, nullptr); + } + scan.skip_bytes(2); + + int thread_index; + while ((thread_index = scan.get_int16()) != -1) { + std::string name = scan.get_string(); + define_thread(thread_index, name, true); + + _threads[thread_index]._data->read_datagram(scan, this); + } + + update_toplevel_collectors(); +} + +/** + * Makes sure there is an entry in the array for a collector with the given + * index number. + */ +void PStatClientData:: +slot_collector(int collector_index) { + // A sanity check on the index number. + nassertv(collector_index < 100000); + + while ((int)_collectors.size() <= collector_index) { + Collector collector; + collector._def = nullptr; + _collectors.push_back(collector); + } +} + +/** + * Rebuilds the list of toplevel collectors. + */ +void PStatClientData:: +update_toplevel_collectors() { + _toplevel_collectors.clear(); + + for (Collector &collector : _collectors) { + PStatCollectorDef *def = collector._def; + if (def != nullptr && def->_parent_index == 0) { + _toplevel_collectors.push_back(def->_index); + } + } +} diff --git a/pandatool/src/pstatserver/pStatClientData.h b/pandatool/src/pstatserver/pStatClientData.h new file mode 100644 index 00000000..bd2bb1d6 --- /dev/null +++ b/pandatool/src/pstatserver/pStatClientData.h @@ -0,0 +1,119 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatClientData.h + * @author drose + * @date 2000-07-11 + */ + +#ifndef PSTATCLIENTDATA_H +#define PSTATCLIENTDATA_H + +#include "pandatoolbase.h" + +#include "pStatThreadData.h" + +#include "pStatClientVersion.h" +#include "referenceCount.h" +#include "pointerTo.h" +#include "bitArray.h" + +#include "pvector.h" +#include "vector_int.h" + +class PStatReader; + +/** + * The data associated with a particular client, but not with any one + * particular frame or thread: the list of collectors and threads, for + * instance. + */ +class PStatClientData : public PStatClientVersion { +public: + PStatClientData() = default; + PStatClientData(PStatReader *reader); + ~PStatClientData(); + + void clear_dirty() const; + bool is_dirty() const; + + bool is_alive() const; + void close(); + + double get_latest_time() const; + + int get_num_collectors() const; + bool has_collector(int index) const; + int find_collector(const std::string &fullname) const; + const PStatCollectorDef &get_collector_def(int index) const; + std::string get_collector_name(int index) const; + std::string get_collector_fullname(int index) const; + bool set_collector_has_level(int index, int thread_index, bool flag); + bool get_collector_has_level(int index, int thread_index) const; + + int get_num_toplevel_collectors() const; + int get_toplevel_collector(int index) const; + + int get_num_threads() const; + bool has_thread(int index) const; + int find_thread(const std::string &name) const; + std::string get_thread_name(int index) const; + const PStatThreadData *get_thread_data(int index) const; + bool is_thread_alive(int index) const; + + int get_child_distance(int parent, int child) const; + + + void add_collector(PStatCollectorDef *def); + void define_thread(int thread_index, const std::string &name = std::string(), + bool mark_alive = false); + void expire_thread(int thread_index); + void remove_thread(int thread_index); + + void record_new_frame(int thread_index, int frame_number, + PStatFrameData *frame_data); + + void write_json(std::ostream &out, int pid = 0) const; + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan); + +private: + void slot_collector(int collector_index); + void update_toplevel_collectors(); + +private: + bool _is_alive = false; + mutable bool _is_dirty = false; + PStatReader *_reader = nullptr; + + class Collector { + public: + PStatCollectorDef *_def; + BitArray _is_level; + }; + + typedef pvector Collectors; + Collectors _collectors; + + typedef vector_int ToplevelCollectors; + ToplevelCollectors _toplevel_collectors; + + class Thread { + public: + std::string _name; + PT(PStatThreadData) _data; + bool _is_alive = false; + }; + typedef pvector Threads; + Threads _threads; + + static PStatCollectorDef _null_collector; + friend class PStatReader; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatFlameGraph.I b/pandatool/src/pstatserver/pStatFlameGraph.I new file mode 100644 index 00000000..e99afd04 --- /dev/null +++ b/pandatool/src/pstatserver/pStatFlameGraph.I @@ -0,0 +1,137 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatFlameGraph.I + * @author rdb + * @date 2022-01-28 + */ + +/** + * Returns the particular thread whose data this flame graph reflects. + */ +INLINE int PStatFlameGraph:: +get_thread_index() const { + return _thread_index; +} + +/** + * Returns the particular collector whose data this flame graph reflects. + */ +INLINE int PStatFlameGraph:: +get_collector_index() const { + return _collector_index; +} + +/** + * Clears the history stack. + */ +INLINE void PStatFlameGraph:: +clear_history() { + _history.clear(); +} + +/** + * Returns the depth of the history stack. + */ +INLINE size_t PStatFlameGraph:: +get_history_depth() const { + return _history.size(); +} + +/** + * Returns the particular frame number whose data this flame graph reflects. + * Returns -1 if we're looking at a moving average instead. + */ +INLINE int PStatFlameGraph:: +get_frame_number() const { + return _frame_number; +} + +/** + * Returns the amount of total time the width of the horizontal axis + * represents. + */ +INLINE double PStatFlameGraph:: +get_horizontal_scale() const { + return _time_width; +} + +/** + * Changes the average_mode flag. When true, the strip chart will average out + * the color values over pstats_average_time seconds, which hides spikes and + * makes the overall trends easier to read. When false, the strip chart shows + * the actual data as it is happening. + * + * If you set this to true, you need to call animate() periodically so that the + * averages are smoothly updated over time. + */ +INLINE void PStatFlameGraph:: +set_average_mode(bool average_mode) { + if (_average_mode != average_mode) { + _average_mode = average_mode; + _stack.reset_averages(); + if (!average_mode) { + _time_width = _stack.get_net_value(false); + normal_guide_bars(); + } + force_redraw(); + } +} + +/** + * Returns the current state of the average_mode flag. When true, the strip + * chart will average out the color values over pstats_average_time seconds, + * which hides spikes and makes the overall trends easier to read. When + * false, the strip chart shows the actual data as it is happening. + */ +INLINE bool PStatFlameGraph:: +get_average_mode() const { + return _average_mode; +} + +/** + * Converts a value (i.e. a "height" in the strip chart) to a horizontal + * pixel offset. + */ +INLINE int PStatFlameGraph:: +height_to_pixel(double value) const { + return (int)((double)_xsize * value / _time_width); +} + +/** + * Converts a horizontal pixel offset to a value (a "height" in the strip + * chart). + */ +INLINE double PStatFlameGraph:: +pixel_to_height(int x) const { + return _time_width * (double)x / (double)_xsize; +} + +/** + * Returns true if get_title_text() has never yet returned an answer, false if + * it has. + */ +INLINE bool PStatFlameGraph:: +is_title_unknown() const { + return _title_unknown; +} +/** + * Returns the net value of this stack level. + */ +INLINE double PStatFlameGraph::StackLevel:: +get_net_value(bool average) const { + if (_collector_index >= 0) { + return average ? _avg_net_value : _net_value; + } else { + double sum = 0.0; + for (auto &item : _children) { + sum += item.second.get_net_value(average); + } + return sum; + } +} diff --git a/pandatool/src/pstatserver/pStatFlameGraph.cxx b/pandatool/src/pstatserver/pStatFlameGraph.cxx new file mode 100644 index 00000000..93436829 --- /dev/null +++ b/pandatool/src/pstatserver/pStatFlameGraph.cxx @@ -0,0 +1,773 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatFlameGraph.cxx + * @author rdb + * @date 2022-01-28 + */ + +#include "pStatFlameGraph.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "string_utils.h" +#include "config_pstatclient.h" + +#include +#include + +/** + * + */ +PStatFlameGraph:: +PStatFlameGraph(PStatMonitor *monitor, int thread_index, int collector_index, + int frame_number, int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize), + _thread_index(thread_index), + _collector_index(collector_index), + _orig_collector_index(collector_index), + _frame_number(frame_number) +{ + _average_mode = true; + _average_cursor = 0; + _current_frame = -1; + + _title_unknown = true; + + // NB. This won't call force_redraw() (which we can't do yet) because average + // mode is true + update(); + _time_width = _stack.get_net_value(false); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + + _guide_bar_units = GBU_ms | GBU_hz | GBU_show_units; + + monitor->_flame_graphs.insert(this); +} + +/** + * + */ +PStatFlameGraph:: +~PStatFlameGraph() { + _monitor->_flame_graphs.erase(this); +} + +/** + * Updates the chart with the latest data. + */ +void PStatFlameGraph:: +update() { + const PStatClientData *client_data = _monitor->get_client_data(); + + // Don't bother to update the thread data until we know at least something + // about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = + client_data->get_thread_data(_thread_index); + if (!thread_data->is_empty()) { + if (_frame_number >= 0) { + if (thread_data->has_frame(_frame_number)) { + if (_current_frame != _frame_number) { + _current_frame = _frame_number; + update_data(); + } + } + } else { + int frame_number = thread_data->get_latest_frame_number(); + if (frame_number != _current_frame) { + _current_frame = frame_number; + + update_data(); + } + } + } + } + + idle(); +} + +/** + * Changes the collector represented by this flame graph. This may force a + * redraw. + * + * Leaves the history stack untouched. + */ +void PStatFlameGraph:: +set_collector_index(int collector_index) { + if (collector_index == -1) { + // First go back to the collector where we originally opened this graph, + // and only then go back to the root. + collector_index = _orig_collector_index; + if (_collector_index == _orig_collector_index) { + collector_index = -1; + _orig_collector_index = -1; + } + } + if (_collector_index != collector_index) { + _collector_index = collector_index; + _title_unknown = true; + _stack.clear(); + update_data(); + + if (_average_mode) { + _stack.update_averages(_average_cursor); + _time_width = _stack.get_net_value(true); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + } + } +} + +/** + * Goes to a different collector, but remembers the previous collector. + */ +void PStatFlameGraph:: +push_collector_index(int collector_index) { + if (_collector_index != collector_index) { + _history.push_back(_collector_index); + set_collector_index(collector_index); + } +} + +/** + * Goes to the previous visited collector. Returns true if the history stack + * was non-empty. + */ +bool PStatFlameGraph:: +pop_collector_index() { + if (!_history.empty()) { + int collector_index = _history.back(); + _history.pop_back(); + set_collector_index(collector_index); + return true; + } + return false; +} + +/** + * Changes the frame number shown by this flame graph. This may force a redraw. + */ +void PStatFlameGraph:: +set_frame_number(int frame_number) { + if (_frame_number != frame_number) { + _frame_number = frame_number; + _current_frame = frame_number; + _title_unknown = true; + _stack.clear(); + update_data(); + + if (_average_mode) { + _stack.update_averages(_average_cursor); + _time_width = _stack.get_net_value(true); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + } + } +} + +/** + * Sets the frame number to the oldest available frame. + */ +bool PStatFlameGraph:: +first_frame() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data == nullptr) { + return false; + } + + const PStatThreadData *thread_data = client_data->get_thread_data(_thread_index); + if (thread_data == nullptr || thread_data->is_empty()) { + return false; + } + + int oldest = thread_data->get_oldest_frame_number(); + if (_frame_number != oldest && thread_data->has_frame(oldest)) { + set_frame_number(oldest); + return true; + } + return false; +} + +/** + * Advances to the next available frame. Returns true if the frame number was + * changed after a call to this method. + */ +bool PStatFlameGraph:: +next_frame() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data == nullptr) { + return false; + } + + const PStatThreadData *thread_data = client_data->get_thread_data(_thread_index); + if (thread_data == nullptr || thread_data->is_empty()) { + return false; + } + + int latest = thread_data->get_latest_frame_number(); + if (_frame_number < 0 || _frame_number > latest) { + set_frame_number(latest); + return true; + } + + for (int i = _frame_number + 1; i <= latest; ++i) { + if (thread_data->has_frame(i)) { + set_frame_number(i); + return true; + } + } + + return false; +} + +/** + * Reverts to the previous frame. Returns true if the frame number was changed + * after a call to this method. + */ +bool PStatFlameGraph:: +prev_frame() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data == nullptr) { + return false; + } + + const PStatThreadData *thread_data = client_data->get_thread_data(_thread_index); + if (thread_data == nullptr || thread_data->is_empty()) { + return false; + } + + int oldest = thread_data->get_oldest_frame_number(); + + int i; + if (_frame_number < 0) { + i = thread_data->get_latest_frame_number(); + } else { + i = _frame_number - 1; + } + while (i >= oldest) { + if (thread_data->has_frame(i)) { + set_frame_number(i); + return true; + } + --i; + } + + return false; +} + +/** + * Sets the frame number to the latest available frame. + */ +bool PStatFlameGraph:: +last_frame() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data == nullptr) { + return false; + } + + const PStatThreadData *thread_data = client_data->get_thread_data(_thread_index); + if (thread_data == nullptr || thread_data->is_empty()) { + return false; + } + + int latest = thread_data->get_latest_frame_number(); + if (_frame_number != latest && thread_data->has_frame(latest)) { + set_frame_number(latest); + return true; + } + return false; +} + +/** + * Returns the text suitable for the title label on the top line. + */ +std::string PStatFlameGraph:: +get_title_text() { + std::string text; + + _title_unknown = false; + + const PStatClientData *client_data = _monitor->get_client_data(); + if (_collector_index >= 0) { + if (client_data->has_collector(_collector_index)) { + text = client_data->get_collector_fullname(_collector_index); + text += " flame graph"; + } else { + _title_unknown = true; + } + + if (_thread_index != 0) { + if (client_data->has_thread(_thread_index)) { + text += " (" + client_data->get_thread_name(_thread_index) + " thread)"; + } else { + _title_unknown = true; + } + } + } + else if (client_data->has_thread(_thread_index)) { + text += client_data->get_thread_name(_thread_index) + " thread flame graph"; + } + else { + _title_unknown = true; + } + + if (_frame_number >= 0) { + text += " (frame " + format_string(_frame_number) + ")"; + } + + return text; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string PStatFlameGraph:: +get_bar_tooltip(int depth, int x) const { + const StackLevel *level = _stack.locate(depth, pixel_to_height(x), _average_mode); + if (level != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->has_collector(level->_collector_index)) { + std::ostringstream text; + text << client_data->get_collector_fullname(level->_collector_index); + text << " (" << format_number(level->get_net_value(_average_mode), GBU_show_units | GBU_ms); + if (level->_count > 1) { + text << " / " << level->_count << "x"; + } + text << ")"; + return text.str(); + } + } + return std::string(); +} + +/** + * Returns the collector index corresponding to the bar at the given location. + */ +int PStatFlameGraph:: +get_bar_collector(int depth, int x) const { + const StackLevel *level = _stack.locate(depth, pixel_to_height(x), _average_mode); + if (level != nullptr) { + return level->_collector_index; + } + return -1; +} + +/** + * Writes the graph state to a datagram. + */ +void PStatFlameGraph:: +write_datagram(Datagram &dg) const { + dg.add_int16(_orig_collector_index); + dg.add_float64(_time_width); + dg.add_bool(_average_mode); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatFlameGraph:: +read_datagram(DatagramIterator &scan) { + _orig_collector_index = scan.get_int16(); + _time_width = scan.get_float64(); + _average_mode = scan.get_bool(); + + PStatGraph::read_datagram(scan); + + _current_frame = -1; + _stack.clear(); + update(); + if (_average_mode) { + _time_width = _stack.get_net_value(false); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + force_redraw(); + } +} + +/** + * + */ +void PStatFlameGraph:: +update_data() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data == nullptr) { + return; + } + + const PStatThreadData *thread_data = client_data->get_thread_data(_thread_index); + if (thread_data == nullptr || thread_data->is_empty()) { + return; + } + + const PStatFrameData &frame_data = thread_data->get_frame(_current_frame); + + bool first_time = _stack._children.empty(); + + StackLevel *top = &_stack; + top->reset(); + + size_t num_events = frame_data.get_num_events(); + for (size_t ei = 0; ei < num_events; ++ei) { + int collector_index = frame_data.get_time_collector(ei); + double time = frame_data.get_time(ei); + + if (frame_data.is_start(ei)) { + // If we have a collector index, use it to determine which bottom-level + // stack frames we are interested in. + if (_collector_index < 0 || + _collector_index == collector_index || + top != &_stack) { + top = top->start(collector_index, time); + } + else { + // Check whether one of the parents matches, perhaps. + int parent_index = collector_index; + do { + const PStatCollectorDef &def = client_data->get_collector_def(parent_index); + if (parent_index == def._parent_index) { + break; + } + parent_index = def._parent_index; + if (parent_index == _collector_index) { + // Yes, let it through. + top = top->start(collector_index, time); + break; + } + } + while (parent_index >= 0 && client_data->has_collector(parent_index)); + } + } + else { + top = top->stop(collector_index, time); + } + } + top = top->stop_all(frame_data.get_end()); + nassertv(top == &_stack); + + if (first_time) { + _stack.reset_averages(); + } + + if (!_average_mode) { + // Redraw right away, except in average mode, where it's done in animate(). + _time_width = _stack.get_net_value(false); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + force_redraw(); + } +} + +/** + * To be called by the user class when the widget size has changed. This + * updates the chart's internal data and causes it to issue redraw commands to + * reflect the new size. + */ +void PStatFlameGraph:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _xsize = xsize; + _ysize = ysize; + + normal_guide_bars(); + force_redraw(); + } +} + +/** + * To be called by the user class when the whole thing needs to be redrawn for + * some reason. + */ +void PStatFlameGraph:: +force_redraw() { + begin_draw(); + r_draw_level(_stack); + end_draw(); +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void PStatFlameGraph:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + int num_bars = get_xsize() / 100; + + _guide_bars.clear(); + + double dist = _time_width / num_bars; + + for (int i = 1; i < num_bars; ++i) { + _guide_bars.push_back(make_guide_bar(i * dist)); + } + + _guide_bars_changed = true; +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any bars in the chart. + */ +void PStatFlameGraph:: +begin_draw() { +} + +/** + * Should be overridden by the user class. Should draw a single bar at the + * indicated location. + */ +void PStatFlameGraph:: +draw_bar(int depth, int from_x, int to_x, int collector_index, int parent_index) { +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the chart. + */ +void PStatFlameGraph:: +end_draw() { +} + +/** + * Should be overridden by the user class to perform any other updates might + * be necessary after the bars have been redrawn. + */ +void PStatFlameGraph:: +idle() { +} + +/** + * Should be called periodically to update any animated values. Returns false + * to indicate that the animation is done and no longer needs to be called. + */ +bool PStatFlameGraph:: +animate(double time, double dt) { + if (!_average_mode) { + return false; + } + + if (_stack.update_averages(_average_cursor)) { + _time_width = _stack.get_net_value(true); + if (_time_width == 0.0) { + _time_width = 1.0 / pstats_target_frame_rate; + } + normal_guide_bars(); + } + + // Always use force_redraw, since the mouse position may have changed. + force_redraw(); + + // Cycle through the ring buffers. + _average_cursor = (_average_cursor + 1) % _num_average_frames; + return true; +} + +/** + * Resets all the nodes by setting their _net_value to 0.0. + */ +void PStatFlameGraph::StackLevel:: +reset() { + _start_time = 0.0; + _net_value = 0.0; + _count = 0; + _started = false; + + for (auto &item : _children) { + item.second.reset(); + } +} + +/** + * Starts the given collector, which starts a new stack frame as a child of + * the current one. Returns the new child, which is the new stack top. + */ +PStatFlameGraph::StackLevel *PStatFlameGraph::StackLevel:: +start(int collector_index, double time) { + StackLevel &child = _children[collector_index]; + child._parent = this; + child._collector_index = collector_index; + child._start_time = std::max(_start_time, time); + child._count++; + child._started = true; + return &child; +} + +/** + * Stops the given collector, which is assumed to be somewhere up the + * hierarchy. Should only be called on the top of the stack, usually. + * Returns the new top of the stack. + */ +PStatFlameGraph::StackLevel *PStatFlameGraph::StackLevel:: +stop(int collector_index, double time) { + StackLevel *new_top = r_stop(collector_index, time); + if (new_top != nullptr) { + return new_top; + } + // We have a stop event without a preceding start event. Measure the + // the time from the start of the current stack frame to the stop time. + // Actually, don't do this, because it means that a child may end up with + // more time than the parent. Need a better solution for this - or not? + //start(collector_index, _start_time)->stop(collector_index, time); + return this; +} + +/** + * Stops all still started collectors. Returns the bottom of the stack, + * which is also the new top of the stack. + */ +PStatFlameGraph::StackLevel *PStatFlameGraph::StackLevel:: +stop_all(double time) { + if (_parent != nullptr) { + nassertr(_started, this); + _net_value += time - _start_time; + return _parent->stop_all(time); + } else { + return this; + } +} + +/** + * Resets the average calculator, used when first enabling average mode. + */ +void PStatFlameGraph::StackLevel:: +reset_averages() { + double net_value = get_net_value(false); + + for (double &value : _values) { + value = net_value; + } + _avg_net_value = net_value; + + for (auto &item : _children) { + item.second.reset_averages(); + } +} + +/** + * Recursively calculates the averages. Returns true if any value was changed. + */ +bool PStatFlameGraph::StackLevel:: +update_averages(size_t cursor) { + _values[cursor] = get_net_value(false); + + bool changed = false; + + double sum = 0; + for (double value : _values) { + sum += value; + } + double avg = sum / _num_average_frames; + if (avg != _avg_net_value) { + _avg_net_value = avg; + changed = true; + } + + for (auto &item : _children) { + if (item.second.update_averages(cursor)) { + changed = true; + } + } + + return changed; +} + +/** + * Locates a stack level at the given depth and the given time offset. + */ +const PStatFlameGraph::StackLevel *PStatFlameGraph::StackLevel:: +locate(int depth, double time, bool average) const { + if (time < 0.0) { + return nullptr; + } + for (const auto &item : _children) { + double value = item.second.get_net_value(average); + if (time < value) { + if (depth == 0) { + // This is it. + return &item.second; + } else { + // Recurse. + return item.second.locate(depth - 1, time, average); + } + } + time -= value; + } + return nullptr; +} + +/** + * Clears everything. + */ +void PStatFlameGraph::StackLevel:: +clear() { + _children.clear(); + _count = 0; + _net_value = 0.0; +} + +/** + * Recursive helper used by stop(). + */ +PStatFlameGraph::StackLevel *PStatFlameGraph::StackLevel:: +r_stop(int collector_index, double time) { + if (_collector_index == collector_index) { + // Found it. + nassertr(_started, nullptr); + _net_value += time - _start_time; + _started = false; + nassertr(_parent != nullptr, nullptr); + return _parent; + } + else if (_parent != nullptr) { + StackLevel *level = _parent->r_stop(collector_index, time); + if (level != nullptr) { + nassertr(_started, nullptr); + _net_value += time - _start_time; + _started = false; + return level; + } + } + return nullptr; +} + +/** + * Recursively draws a level. + */ +void PStatFlameGraph:: +r_draw_level(const StackLevel &level, int depth, double offset) { + for (const auto &item : level._children) { + const StackLevel &child = item.second; + + double value = child.get_net_value(_average_mode); + + int from_x = height_to_pixel(offset); + int to_x = height_to_pixel(offset + value); + + // No need to recurse if the bars have become smaller than a pixel. + if (to_x > from_x) { + draw_bar(depth, from_x, to_x, child._collector_index, level._collector_index); + r_draw_level(child, depth + 1, offset); + } + + offset += value; + } +} diff --git a/pandatool/src/pstatserver/pStatFlameGraph.h b/pandatool/src/pstatserver/pStatFlameGraph.h new file mode 100644 index 00000000..5e742383 --- /dev/null +++ b/pandatool/src/pstatserver/pStatFlameGraph.h @@ -0,0 +1,150 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatFlameGraph.h + * @author rdb + * @date 2022-01-28 + */ + +#ifndef PSTATFLAMEGRAPH_H +#define PSTATFLAMEGRAPH_H + +#include "pandatoolbase.h" + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include "pmap.h" +#include "pdeque.h" + +class PStatFrameData; + +/** + * This is an abstract class that presents the interface for drawing a flame + * chart: it shows the time spent in each of a number of collectors + * as a horizontal bar of color, with time as the horizontal axis. + * + * This class just manages all the flame chart logic; the actual nuts and bolts + * of drawing pixels is left to a user-derived class. + */ +class PStatFlameGraph : public PStatGraph { +public: + PStatFlameGraph(PStatMonitor *monitor, + int thread_index, int collector_index, int frame_number, + int xsize, int ysize); + virtual ~PStatFlameGraph(); + + void update(); + + INLINE int get_thread_index() const; + INLINE int get_collector_index() const; + void set_collector_index(int collector_index); + void push_collector_index(int collector_index); + bool pop_collector_index(); + INLINE void clear_history(); + INLINE size_t get_history_depth() const; + + INLINE int get_frame_number() const; + void set_frame_number(int collector_index); + bool first_frame(); + bool next_frame(); + bool prev_frame(); + bool last_frame(); + + INLINE double get_horizontal_scale() const; + + INLINE void set_average_mode(bool average_mode); + INLINE bool get_average_mode() const; + + INLINE int height_to_pixel(double value) const; + INLINE double pixel_to_height(int y) const; + + INLINE bool is_title_unknown() const; + std::string get_title_text(); + + std::string get_bar_tooltip(int depth, int x) const; + int get_bar_collector(int depth, int x) const; + + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + +protected: + void update_data(); + void changed_size(int xsize, int ysize); + void force_redraw(); + virtual void normal_guide_bars(); + + virtual void begin_draw(); + virtual void draw_bar(int depth, int from_x, int to_x, + int collector_index, int parent_index); + virtual void end_draw(); + virtual void idle(); + + bool animate(double time, double dt); + +private: + static const size_t _num_average_frames = 150; + + class StackLevel { + public: + void reset(); + + StackLevel *start(int collector_index, double time); + StackLevel *stop(int collector_index, double time); + StackLevel *stop_all(double time); + + INLINE double get_net_value(bool average) const; + void reset_averages(); + bool update_averages(size_t cursor); + + const StackLevel *locate(int depth, double time, bool average) const; + + void clear(); + + private: + StackLevel *r_stop(int collector_index, double time); + + double _net_value = 0.0; + double _avg_net_value = 0.0; + + // This is updated like a ring buffer, initialized with all the same value + // at first, then always at _average_cursor. + double _values[_num_average_frames] = {0.0}; + + double _start_time = 0.0; + int _count = 0; + bool _started = false; + + int _collector_index = -1; + StackLevel *_parent = nullptr; + pmap _children; + + friend class PStatFlameGraph; + }; + + void r_draw_level(const StackLevel &level, int depth = 0, double offset = 0.0); + + StackLevel _stack; + int _thread_index; + int _collector_index; + int _orig_collector_index; + int _frame_number; + bool _average_mode; + size_t _average_cursor; + + double _time_width; + int _current_frame; + bool _title_unknown; + + std::vector _history; +}; + +#include "pStatFlameGraph.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatGraph.I b/pandatool/src/pstatserver/pStatGraph.I new file mode 100644 index 00000000..3bda0ced --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.I @@ -0,0 +1,131 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatGraph.I + * @author drose + * @date 2000-07-19 + */ + +/** + * Returns the monitor associated with this chart. + */ +INLINE PStatMonitor *PStatGraph:: +get_monitor() const { + return _monitor; +} + +/** + * Returns the number of labels to be drawn for this chart. + */ +INLINE int PStatGraph:: +get_num_labels() const { + return _labels.size(); +} + +/** + * Returns the collector index associated with the nth label. + */ +INLINE int PStatGraph:: +get_label_collector(int n) const { + nassertr(n >= 0 && n < (int)_labels.size(), 0); + return _labels[n]; +} + +/** + * Returns the text associated with the nth label. + */ +INLINE std::string PStatGraph:: +get_label_name(int n) const { + nassertr(n >= 0 && n < (int)_labels.size(), std::string()); + return _monitor->get_client_data()->get_collector_name(_labels[n]); +} + +/** + * Returns the color associated with the nth label. + */ +INLINE LRGBColor PStatGraph:: +get_label_color(int n) const { + nassertr(n >= 0 && n < (int)_labels.size(), LRGBColor(0.0, 0.0, 0.0)); + return _monitor->get_collector_color(_labels[n]); +} + +/** + * Sets the target frame rate of the application in Hz. This only affects the + * choice of initial scale and the placement of guide bars. + */ +INLINE void PStatGraph:: +set_target_frame_rate(double frame_rate) { + if (_target_frame_rate != frame_rate) { + _target_frame_rate = frame_rate; + normal_guide_bars(); + } +} + +/** + * Returns the indicated target frame rate in Hz. See + * set_target_frame_rate(). + */ +INLINE double PStatGraph:: +get_target_frame_rate() const { + return _target_frame_rate; +} + +/** + * Returns the width of the chart in pixels. + */ +INLINE int PStatGraph:: +get_xsize() const { + return _xsize; +} + +/** + * Returns the height of the chart in pixels. + */ +INLINE int PStatGraph:: +get_ysize() const { + return _ysize; +} + +/** + * Sets the units that are displayed for the guide bar labels. This may be a + * union of one or more members of the GuideBarUnits enum. + */ +INLINE void PStatGraph:: +set_guide_bar_units(int guide_bar_units) { + if (_guide_bar_units != guide_bar_units) { + _guide_bar_units = guide_bar_units; + normal_guide_bars(); + } +} + +/** + * Returns the units that are displayed for the guide bar labels. This may be + * a union of one or more members of the GuideBarUnits enum. + */ +INLINE int PStatGraph:: +get_guide_bar_units() const { + return _guide_bar_units; +} + +/** + * Sets the name of the units to be used for the guide bars if the units type + * is set to GBU_named | GBU_show_units. + */ +INLINE void PStatGraph:: +set_guide_bar_unit_name(const std::string &unit_name) { + _unit_name = unit_name; +} + +/** + * Returns the name of the units to be used for the guide bars if the units + * type is set to GBU_named | GBU_show_units. + */ +INLINE const std::string &PStatGraph:: +get_guide_bar_unit_name() const { + return _unit_name; +} diff --git a/pandatool/src/pstatserver/pStatGraph.cxx b/pandatool/src/pstatserver/pStatGraph.cxx new file mode 100644 index 00000000..a0d1a5da --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.cxx @@ -0,0 +1,371 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatGraph.cxx + * @author drose + * @date 2000-07-19 + */ + +#include "pStatGraph.h" +#include "pStatServer.h" +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "string_utils.h" +#include "config_pstatclient.h" + +#include // for sprintf + +using std::string; + +/** + * + */ +PStatGraph::GuideBar:: +GuideBar(double height, const string &label, PStatGraph::GuideBarStyle style) : + _height(height), + _label(label), + _style(style) +{ +} + +/** + * + */ +PStatGraph::GuideBar:: +GuideBar(const PStatGraph::GuideBar ©) : + _height(copy._height), + _label(copy._label), + _style(copy._style) +{ +} + +/** + * + */ +PStatGraph:: +PStatGraph(PStatMonitor *monitor, int xsize, int ysize) : + _monitor(monitor), + _xsize(xsize), + _ysize(ysize) +{ + _target_frame_rate = pstats_target_frame_rate; + _labels_changed = false; + _guide_bars_changed = false; + _guide_bar_units = GBU_ms; +} + +/** + * + */ +PStatGraph:: +~PStatGraph() { +} + +/** + * Returns the number of horizontal guide bars that should be drawn, based on + * the indicated target frame rate. Not all of these may be visible; some may + * be off the top of the chart because of the vertical scale. + */ +int PStatGraph:: +get_num_guide_bars() const { + return _guide_bars.size(); +} + +/** + * Returns the nth horizontal guide bar. This should be drawn as a horizontal + * line across the chart at the y pixel location determined by + * height_to_pixel(bar._height). + * + * It is possible that this bar will be off the top of the chart. + */ +const PStatGraph::GuideBar &PStatGraph:: +get_guide_bar(int n) const { +#ifndef NDEBUG + static GuideBar bogus_bar(0.0, "bogus", GBS_normal); + nassertr(n >= 0 && n < (int)_guide_bars.size(), bogus_bar); +#endif + return _guide_bars[n]; +} + +/** + * Returns the current number of user-defined guide bars. Not all of these + * may be visible. + */ +int PStatGraph:: +get_num_user_guide_bars() const { + return _monitor->get_server()->get_num_user_guide_bars(); +} + +/** + * Returns the nth user-defined guide bar. + */ +PStatGraph::GuideBar PStatGraph:: +get_user_guide_bar(int n) const { + double height = _monitor->get_server()->get_user_guide_bar_height(n); + return make_guide_bar(height, GBS_user); +} + +/** + * Adjusts the height of the nth user-defined guide bar. + */ +void PStatGraph:: +move_user_guide_bar(int n, double height) { + _monitor->get_server()->move_user_guide_bar(n, height); +} + +/** + * Creates a new user guide bar and returns its index number. + */ +int PStatGraph:: +add_user_guide_bar(double height) { + return _monitor->get_server()->add_user_guide_bar(height); +} + +/** + * Removes the user guide bar with the indicated index number. All subsequent + * index numbers are adjusted down one. + */ +void PStatGraph:: +remove_user_guide_bar(int n) { + _monitor->get_server()->remove_user_guide_bar(n); +} + +/** + * Returns the index number of the first user guide bar found whose height is + * within the indicated range, or -1 if no user guide bars fall within the + * range. + */ +int PStatGraph:: +find_user_guide_bar(double from_height, double to_height) const { + return _monitor->get_server()->find_user_guide_bar(from_height, to_height); +} + + +/** + * Returns a string representing the value nicely formatted for its range. + */ +string PStatGraph:: +format_number(double value) { + char buffer[128]; + + if (value < 0.01) { + sprintf(buffer, "%0.4f", value); + } else if (value < 0.1) { + sprintf(buffer, "%0.3f", value); + } else if (value < 1.0) { + sprintf(buffer, "%0.2f", value); + } else if (value < 10.0) { + sprintf(buffer, "%0.1f", value); + } else { + sprintf(buffer, "%0.0f", value); + } + + return buffer; +} + +/** + * Returns a string representing the value nicely formatted for its range, + * including the units as indicated. + */ +string PStatGraph:: +format_number(double value, int guide_bar_units, const string &unit_name) { + string label; + + if ((guide_bar_units & GBU_named) != 0) { + // Units are whatever is specified by unit_name, not a time unit at all. + int int_value = (int)value; + if ((double)int_value == value) { + // Probably a counter or something, don't display .0 suffix. + label = format_string(int_value); + } else { + label = format_number(value); + } + if ((guide_bar_units & GBU_show_units) != 0 && !unit_name.empty()) { + label += " "; + label += unit_name; + } + } + else { + // Units are either milliseconds or hz, or both. + if ((guide_bar_units & GBU_ms) != 0) { + if ((guide_bar_units & GBU_show_units) != 0 && + value > 0 && value < 0.000001) { + double ns = value * 1000000000.0; + label += format_number(ns); + label += " ns"; + } + else if ((guide_bar_units & GBU_show_units) != 0 && + value > 0 && value < 0.001) { + double us = value * 1000000.0; + label += format_number(us); +#ifdef _WIN32 + label += " \xb5s"; +#else + label += " \xc2\xb5s"; +#endif + } + else if ((guide_bar_units & GBU_show_units) == 0 || value < 1.0) { + double ms = value * 1000.0; + label += format_number(ms); + if ((guide_bar_units & GBU_show_units) != 0) { + label += " ms"; + } + } + else { + label += format_number(value); + label += " s"; + } + } + + if ((guide_bar_units & GBU_hz) != 0) { + double hz = 1.0 / value; + + if ((guide_bar_units & GBU_show_units) != 0 && + (guide_bar_units & GBU_ms) == 0) { + if (hz >= 1000000000) { + label += format_number(hz / 1000000000); + label += " GHz"; + } + else if (hz >= 1000000) { + label += format_number(hz / 1000000); + label += " MHz"; + } + else if (hz >= 1000) { + label += format_number(hz / 1000); + label += " kHz"; + } + else { + label += format_number(hz); + label += " Hz"; + } + } + else { + if ((guide_bar_units & GBU_ms) != 0) { + label += " ("; + } + label += format_number(hz); + if ((guide_bar_units & GBU_show_units) != 0) { + label += " Hz"; + } + if ((guide_bar_units & GBU_ms) != 0) { + label += ")"; + } + } + } + } + + return label; +} + +/** + * Writes the graph state to a datagram. + */ +void PStatGraph:: +write_datagram(Datagram &dg) const { + int x, y, width, height; + bool minimized, maximized; + if (get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_bool(true); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + else { + dg.add_bool(false); + } +} + +/** + * Restores the graph state from a datagram. + */ +void PStatGraph:: +read_datagram(DatagramIterator &scan) { + if (scan.get_bool()) { + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + set_window_state(x, y, width, height, minimized, maximized); + } +} + +/** + * Resets the list of guide bars. + */ +void PStatGraph:: +update_guide_bars(int num_bars, double scale) { + _guide_bars.clear(); + + // We'd like to draw about num_bars bars on the chart. But we also want the + // bars to be harmonics of the target frame rate, so that the bottom bar is + // at tfrn or n * tfr, where n is an integer, and the upper bars are even + // multiples of that. + + // Choose a suitable harmonic of the target frame rate near the bottom part + // of the chart. + + double bottom = (double)num_bars / scale; + + double harmonic; + if (_target_frame_rate < bottom) { + // n * tfr + harmonic = floor(bottom / _target_frame_rate + 0.5) * _target_frame_rate; + + } else { + // tfr n + harmonic = _target_frame_rate / floor(_target_frame_rate / bottom + 0.5); + } + + // Now, make a few bars at k harmonic. + for (int k = 1; k / harmonic <= scale; k++) { + _guide_bars.push_back(make_guide_bar(k / harmonic)); + } + + _guide_bars_changed = true; +} + +/** + * Makes a guide bar for the indicated elapsed time or level units. + */ +PStatGraph::GuideBar PStatGraph:: +make_guide_bar(double value, PStatGraph::GuideBarStyle style) const { + string label = format_number(value, _guide_bar_units, _unit_name); + + if ((style == GBS_normal) && + (_guide_bar_units & GBU_named) == 0) { + // If it's a time unit, check to see if it matches our target frame rate. + double hz = 1.0 / value; + if (IS_THRESHOLD_EQUAL(hz, _target_frame_rate, 0.001)) { + style = GBS_target; + } + } + + return GuideBar(value, label, style); +} + +/** + * Returns the current window dimensions. + */ +bool PStatGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + return false; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void PStatGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { +} diff --git a/pandatool/src/pstatserver/pStatGraph.h b/pandatool/src/pstatserver/pStatGraph.h new file mode 100644 index 00000000..6c9168a4 --- /dev/null +++ b/pandatool/src/pstatserver/pStatGraph.h @@ -0,0 +1,130 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatGraph.h + * @author drose + * @date 2000-07-19 + */ + +#ifndef PSTATGRAPH_H +#define PSTATGRAPH_H + +#include "pandatoolbase.h" + +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include "luse.h" +#include "vector_int.h" + +#include "pmap.h" + +class PStatView; + +/** + * This is an abstract base class for several different kinds of graphs that + * have a few things in common, like labels and guide bars. + */ +class PStatGraph { +public: + PStatGraph(PStatMonitor *monitor, int xsize, int ysize); + virtual ~PStatGraph(); + + INLINE PStatMonitor *get_monitor() const; + + INLINE int get_num_labels() const; + INLINE int get_label_collector(int n) const; + INLINE std::string get_label_name(int n) const; + INLINE LRGBColor get_label_color(int n) const; + + INLINE void set_target_frame_rate(double frame_rate); + INLINE double get_target_frame_rate() const; + + INLINE int get_xsize() const; + INLINE int get_ysize() const; + + enum GuideBarStyle { + GBS_normal, + GBS_target, + GBS_user, + GBS_frame, + }; + + class GuideBar { + public: + GuideBar(double height, const std::string &label, GuideBarStyle style); + GuideBar(const GuideBar ©); + + double _height; + std::string _label; + GuideBarStyle _style; + }; + + enum GuideBarUnits { + GBU_hz = 0x0001, + GBU_ms = 0x0002, + GBU_named = 0x0004, + GBU_show_units = 0x0008, + }; + + int get_num_guide_bars() const; + const GuideBar &get_guide_bar(int n) const; + + int get_num_user_guide_bars() const; + GuideBar get_user_guide_bar(int n) const; + void move_user_guide_bar(int n, double height); + int add_user_guide_bar(double height); + void remove_user_guide_bar(int n); + int find_user_guide_bar(double from_height, double to_height) const; + + INLINE void set_guide_bar_units(int unit_mask); + INLINE int get_guide_bar_units() const; + INLINE void set_guide_bar_unit_name(const std::string &unit_name); + INLINE const std::string &get_guide_bar_unit_name() const; + + static std::string format_number(double value); + static std::string format_number(double value, int guide_bar_units, + const std::string &unit_name = std::string()); + + virtual void write_datagram(Datagram &dg) const; + virtual void read_datagram(DatagramIterator &scan); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + +protected: + virtual void normal_guide_bars()=0; + void update_guide_bars(int num_bars, double scale); + GuideBar make_guide_bar(double value, GuideBarStyle style = GBS_normal) const; + + bool _labels_changed; + bool _guide_bars_changed; + + PT(PStatMonitor) _monitor; + + double _target_frame_rate; + + int _xsize; + int _ysize; + + // Table of the collectors that should be drawn as labels, in order from + // bottom to top. + typedef vector_int Labels; + Labels _labels; + + typedef pvector GuideBars; + GuideBars _guide_bars; + int _guide_bar_units; + std::string _unit_name; +}; + +#include "pStatGraph.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatListener.cxx b/pandatool/src/pstatserver/pStatListener.cxx new file mode 100644 index 00000000..c2f7bb35 --- /dev/null +++ b/pandatool/src/pstatserver/pStatListener.cxx @@ -0,0 +1,50 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatListener.cxx + * @author drose + * @date 2000-07-09 + */ + +#include "pStatListener.h" +#include "pStatServer.h" +#include "pStatReader.h" + +/** + * + */ +PStatListener:: +PStatListener(PStatServer *manager) : + ConnectionListener(manager, manager->is_thread_safe() ? 1 : 0), + _manager(manager) +{ +} + +/** + * An internal function called by ConnectionListener() when a new TCP + * connection has been established. + */ +void PStatListener:: +connection_opened(const PT(Connection) &, + const NetAddress &address, + const PT(Connection) &new_connection) { + PStatMonitor *monitor = _manager->make_monitor(address); + if (monitor == nullptr) { + nout << "Couldn't create monitor!\n"; + return; + } + + nout << "Got new connection from " << address << "\n"; + + // Make sure this connection doesn't queue up TCP packets we write to it. + new_connection->set_collect_tcp(false); + + PStatReader *reader = new PStatReader(_manager, monitor); + _manager->add_reader(new_connection, reader); + reader->set_tcp_connection(new_connection); +} diff --git a/pandatool/src/pstatserver/pStatListener.h b/pandatool/src/pstatserver/pStatListener.h new file mode 100644 index 00000000..b4d27690 --- /dev/null +++ b/pandatool/src/pstatserver/pStatListener.h @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatListener.h + * @author drose + * @date 2000-07-09 + */ + +#ifndef PSTATLISTENER_H +#define PSTATLISTENER_H + +#include "pandatoolbase.h" + +#include "connectionListener.h" +#include "referenceCount.h" + +class PStatServer; +class PStatMonitor; + +/** + * This is the TCP rendezvous socket listener. We need one of these to listen + * for new connections on the socket(s) added to the PStatServer. + */ +class PStatListener : public ConnectionListener { +public: + PStatListener(PStatServer *manager); + +protected: + virtual void connection_opened(const PT(Connection) &rendezvous, + const NetAddress &address, + const PT(Connection) &new_connection); + +private: + PStatServer *_manager; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatMonitor.I b/pandatool/src/pstatserver/pStatMonitor.I new file mode 100644 index 00000000..482102eb --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.I @@ -0,0 +1,95 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatMonitor.I + * @author drose + * @date 2000-07-14 + */ + +/** + * Returns the server that owns this monitor. + */ +INLINE PStatServer *PStatMonitor:: +get_server() { + return _server; +} + +/** + * Returns the client data associated with this monitor. + */ +INLINE const PStatClientData *PStatMonitor:: +get_client_data() const { + return _client_data; +} + +/** + * Returns the name of the indicated collector, if it is known. + */ +INLINE std::string PStatMonitor:: +get_collector_name(int collector_index) { + if (!_client_data.is_null() && + _client_data->has_collector(collector_index)) { + return _client_data->get_collector_name(collector_index); + } + return "Unknown"; +} + +/** + * Returns true if we've yet received the "hello" message from the client + * indicating its name, etc. + */ +INLINE bool PStatMonitor:: +is_client_known() const { + return _client_known; +} + +/** + * Returns the hostname of the client we're connected to, if known. This may + * not be known immediately at creation time, but should be learned shortly + * thereafter when we receive the client's "hello" message. See + * is_client_known(). + */ +INLINE std::string PStatMonitor:: +get_client_hostname() const { + return _client_hostname; +} + +/** + * Returns the program name of the client we're connected to, if known. This + * may not be known immediately at creation time, but should be learned + * shortly thereafter when we receive the client's "hello" message. See + * is_client_known(). + */ +INLINE std::string PStatMonitor:: +get_client_progname() const { + return _client_progname; +} + +/** + * Returns the process id of the client, or -1 if it is not known. + */ +INLINE int PStatMonitor:: +get_client_pid() const { + return _client_pid; +} + +/** + * Returns true if the session data was loaded from a file. + */ +INLINE bool PStatMonitor:: +has_read_filename() const { + return !_read_filename.empty(); +} + +/** + * Returns the filename this session data was loaded from. + */ +INLINE const Filename &PStatMonitor:: +get_read_filename() const { + return _read_filename; +} diff --git a/pandatool/src/pstatserver/pStatMonitor.cxx b/pandatool/src/pstatserver/pStatMonitor.cxx new file mode 100644 index 00000000..cbc49a71 --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.cxx @@ -0,0 +1,799 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatMonitor.cxx + * @author drose + * @date 2000-07-09 + */ + +#include "pStatMonitor.h" + +#include "datagramInputFile.h" +#include "datagramOutputFile.h" +#include "pandaVersion.h" +#include "pStatCollectorDef.h" +#include "pStatTimeline.h" +#include "pStatStripChart.h" +#include "pStatFlameGraph.h" +#include "pStatPianoRoll.h" + +using std::string; + +static const string session_file_header("pstat\0\n\r", 8); +static const string layout_file_header("pslyt\0\n\r", 8); + +static const Filename layout_filename = Filename::binary_filename( +#ifdef _WIN32 + Filename::expand_from("$USER_APPDATA/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout") +#elif defined(__APPLE__) + Filename::expand_from("$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout") +#else + Filename::expand_from("$XDG_STATE_HOME/panda3d/pstats-layout") +#endif +); + +/** + * + */ +PStatMonitor:: +PStatMonitor(PStatServer *server) : _server(server) { + _client_known = false; +} + +/** + * + */ +PStatMonitor:: +~PStatMonitor() { + close(); +} + +/** + * Called shortly after startup time with the greeting from the client. This + * indicates the client's reported hostname and program name. + */ +void PStatMonitor:: +hello_from(const string &hostname, const string &progname, int pid) { + _client_known = true; + _client_hostname = hostname; + _client_progname = progname; + _client_pid = pid; + got_hello(); +} + +/** + * Called shortly after startup time with the greeting from the client. In + * this case, the client seems to have an incompatible version and will be + * automatically disconnected; the server should issue a message to that + * effect. + */ +void PStatMonitor:: +bad_version(const string &hostname, const string &progname, int pid, + int client_major, int client_minor, + int server_major, int server_minor) { + _client_known = true; + _client_hostname = hostname; + _client_progname = progname; + _client_pid = 0; + got_bad_version(client_major, client_minor, + server_major, server_minor); +} + +/** + * Called by the PStatServer at setup time to set the new data pointer for the + * first time. + */ +void PStatMonitor:: +set_client_data(PStatClientData *client_data) { + _client_data = client_data; + initialized(); +} + +/** + * Writes the data and the UI state to the given file. + */ +bool PStatMonitor:: +write(const Filename &fn) const { + DatagramOutputFile dof; + if (!dof.open(fn)) { + return false; + } + if (!dof.write_header(session_file_header)) { + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + dg.add_uint16(1); + dg.add_uint16(1); + write_datagram(dg); + dof.put_datagram(dg); + dof.close(); + return true; +} + +/** + * Reads the data and the UI state from the given file. + */ +bool PStatMonitor:: +read(const Filename &fn) { + close(); + + DatagramInputFile dif; + if (!dif.open(fn)) { + nout << "Failed to open " << fn << " for reading.\n"; + return false; + } + string header; + if (!dif.read_header(header, 8) || header != session_file_header) { + nout << "Session file contains invalid header.\n"; + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + + if (!dif.get_datagram(dg)) { + nout << "Failed to read datagram from session file.\n"; + return false; + } + + DatagramIterator scan(dg); + int version = scan.get_uint16(); + if (version != 1) { + nout << "Unsupported session file version " << version << ".\n"; + return false; + } + // Room for a minor version number + scan.get_uint16(); + + read_datagram(scan); + dif.close(); + + idle(); + + _read_filename = fn; + _client_data->clear_dirty(); + + return true; +} + +/** + * Opens the default set of graphs. + */ +void PStatMonitor:: +open_default_graphs() { + DatagramInputFile dif; + if (!dif.open(layout_filename)) { + open_strip_chart(0, 0, false); + return; + } + string header; + if (!dif.read_header(header, 8) || header != layout_file_header) { + nout << "Layout file contains invalid header.\n"; + open_strip_chart(0, 0, false); + return; + } + Datagram dg; + dg.set_stdfloat_double(false); + + if (!dif.get_datagram(dg)) { + nout << "Failed to read datagram from layout file.\n"; + open_strip_chart(0, 0, false); + return; + } + + DatagramIterator scan(dg); + int version = scan.get_uint16(); + if (version != 1) { + nout << "Unsupported layout file version " << version << ".\n"; + open_strip_chart(0, 0, false); + return; + } + // Room for a minor version number + scan.get_uint16(); + + size_t num_colors = scan.get_uint32(); + for (size_t i = 0; i < num_colors; ++i) { + int key = scan.get_int32(); + LRGBColor &color = _colors[key]; + color[0] = scan.get_float32(); + color[1] = scan.get_float32(); + color[2] = scan.get_float32(); + } + + PStatGraph *graph; + + size_t num_timelines = scan.get_uint16(); + for (size_t i = 0; i < num_timelines; ++i) { + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + graph = open_timeline(); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + + size_t num_strip_charts = scan.get_uint16(); + for (size_t i = 0; i < num_strip_charts; ++i) { + std::string thread_name = scan.get_string(); + std::string collector_name = scan.get_string(); + bool show_level = scan.get_bool(); + int thread_index = _client_data->find_thread(thread_name); + int collector_index = _client_data->find_collector(collector_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_strip_chart(thread_index, collector_index, show_level); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + size_t num_flame_graphs = scan.get_uint16(); + for (size_t i = 0; i < num_flame_graphs; ++i) { + std::string thread_name = scan.get_string(); + std::string collector_name = scan.get_string(); + int thread_index = _client_data->find_thread(thread_name); + int collector_index = collector_name.empty() ? -1 : _client_data->find_collector(collector_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_flame_graph(thread_index, collector_index); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + size_t num_piano_rolls = scan.get_uint16(); + for (size_t i = 0; i < num_piano_rolls; ++i) { + std::string thread_name = scan.get_string(); + int thread_index = _client_data->find_thread(thread_name); + int x = scan.get_int32(); + int y = scan.get_int32(); + int width = scan.get_int32(); + int height = scan.get_int32(); + bool minimized = scan.get_bool(); + bool maximized = scan.get_bool(); + if (thread_index != -1) { + graph = open_piano_roll(thread_index); + graph->set_window_state(x, y, width, height, minimized, maximized); + } + } + + dif.close(); + if (num_timelines + num_strip_charts + num_flame_graphs + num_piano_rolls == 0) { + open_strip_chart(0, 0, false); + } +} + +/** + * Saves the current graph layout as the default graph layout. + */ +bool PStatMonitor:: +save_default_graphs() const { + layout_filename.make_dir(); + + DatagramOutputFile dof; + if (!dof.open(layout_filename)) { + return false; + } + if (!dof.write_header(layout_file_header)) { + return false; + } + Datagram dg; + dg.set_stdfloat_double(false); + dg.add_uint16(1); + dg.add_uint16(1); + + dg.add_uint32((uint32_t)_colors.size()); + for (const auto &item : _colors) { + dg.add_int32(item.first); + dg.add_float32(item.second[0]); + dg.add_float32(item.second[1]); + dg.add_float32(item.second[2]); + } + + dg.add_uint16(_timelines.size()); + for (PStatGraph *graph : _timelines) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_strip_charts.size()); + for (PStatGraph *graph : _strip_charts) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_string(_client_data->get_thread_name(((PStatStripChart *)graph)->get_thread_index())); + dg.add_string(_client_data->get_collector_fullname(((PStatStripChart *)graph)->get_collector_index())); + dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level()); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_flame_graphs.size()); + for (PStatGraph *graph : _flame_graphs) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + int collector_index = ((PStatFlameGraph *)graph)->get_collector_index(); + dg.add_string(_client_data->get_thread_name(((PStatFlameGraph *)graph)->get_thread_index())); + dg.add_string(collector_index >= 0 ? _client_data->get_collector_fullname(collector_index) : ""); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + dg.add_uint16(_piano_rolls.size()); + for (PStatGraph *graph : _piano_rolls) { + int x, y, width, height; + bool minimized, maximized; + if (graph->get_window_state(x, y, width, height, minimized, maximized)) { + dg.add_string(_client_data->get_thread_name(((PStatPianoRoll *)graph)->get_thread_index())); + dg.add_int32(x); + dg.add_int32(y); + dg.add_int32(width); + dg.add_int32(height); + dg.add_bool(minimized); + dg.add_bool(maximized); + } + } + + // Reserved for future graph type + dg.add_uint16(0); + + dof.put_datagram(dg); + dof.close(); + return true; +} + +/** + * Returns true if the client is alive and connected, false otherwise. + */ +bool PStatMonitor:: +is_alive() const { + if (_client_data.is_null()) { + // Not yet, but in a second probably. + return false; + } + return _client_data->is_alive(); +} + +/** + * Closes the client connection if it is active. + */ +void PStatMonitor:: +close() { + if (!_client_data.is_null()) { + _client_data->close(); + } +} + +/** + * Returns the color associated with the indicated collector. If the + * collector has no associated color, or is unknown, a new color will be made + * up on the spot and associated with this collector for the rest of the + * session. + */ +const LRGBColor &PStatMonitor:: +get_collector_color(int collector_index) { + Colors::iterator ci; + ci = _colors.find(collector_index); + if (ci != _colors.end()) { + return (*ci).second; + } + + // Ask the client data about the color. + if (!_client_data.is_null() && + _client_data->has_collector(collector_index)) { + const PStatCollectorDef &def = + _client_data->get_collector_def(collector_index); + + LRGBColor sc(def._suggested_color.r, + def._suggested_color.g, + def._suggested_color.b); + if (sc != LRGBColor::zero()) { + ci = _colors.insert(Colors::value_type(collector_index, sc)).first; + return (*ci).second; + } + + // Use the fullname of the collector as a hash to seed the random number + // generator (consulted below), so we get the same color for a given name + // across sessions. + string fullname = _client_data->get_collector_fullname(collector_index); + unsigned int hash = 0; + for (string::const_iterator ci = fullname.begin(); ci != fullname.end(); ++ci) { + hash = hash * 37 + (unsigned int)(*ci); + } + srand(hash); + } + + // We didn't have a color for the collector; make one up. + LRGBColor random_color; + random_color[0] = (double)rand() / (double)RAND_MAX; + random_color[1] = (double)rand() / (double)RAND_MAX; + random_color[2] = (double)rand() / (double)RAND_MAX; + + ci = _colors.insert(Colors::value_type(collector_index, random_color)).first; + return (*ci).second; +} + +/** + * Sets a custom color associated with the given collector. + */ +void PStatMonitor:: +set_collector_color(int collector_index, const LRGBColor &color) { + _colors[collector_index] = color; +} + +/** + * Clears any custom custom color associated with the given collector. + */ +void PStatMonitor:: +clear_collector_color(int collector_index) { + _colors.erase(collector_index); +} + +/** + * Returns a view on the given thread index. If there is no such view already + * for the indicated thread, this will create one. This view can be used to + * examine the accumulated data for the given thread. + */ +PStatView &PStatMonitor:: +get_view(int thread_index) { + Views::iterator vi; + vi = _views.find(thread_index); + if (vi == _views.end()) { + vi = _views.insert(Views::value_type(thread_index, PStatView())).first; + (*vi).second.set_thread_data(_client_data->get_thread_data(thread_index)); + } + return (*vi).second; +} + +/** + * Returns a view on the level value (as opposed to elapsed time) for the + * given collector over the given thread. If there is no such view already + * for the indicated thread, this will create one. + */ +PStatView &PStatMonitor:: +get_level_view(int collector_index, int thread_index) { + LevelViews::iterator lvi; + lvi = _level_views.find(collector_index); + if (lvi == _level_views.end()) { + lvi = _level_views.insert(LevelViews::value_type(collector_index, Views())).first; + } + Views &views = (*lvi).second; + + Views::iterator vi; + vi = views.find(thread_index); + if (vi == views.end()) { + vi = views.insert(Views::value_type(thread_index, PStatView())).first; + (*vi).second.set_thread_data(_client_data->get_thread_data(thread_index)); + (*vi).second.constrain(collector_index, true); + } + return (*vi).second; +} + +/** + * Called after the monitor has been fully set up. At this time, it will have + * a valid _client_data pointer, and things like is_alive() and close() will + * be meaningful. However, we may not yet know who we're connected to + * (is_client_known() may return false), and we may not know anything about + * the threads or collectors we're about to get data on. + */ +void PStatMonitor:: +initialized() { +} + +/** + * Called when the "hello" message has been received from the client. At this + * time, the client's hostname and program name will be known. + */ +void PStatMonitor:: +got_hello() { +} + +/** + * Like got_hello(), this is called when the "hello" message has been received + * from the client. At this time, the client's hostname and program name will + * be known. However, the client appears to be an incompatible version and + * the connection will be terminated; the monitor should issue a message to + * that effect. + */ +void PStatMonitor:: +got_bad_version(int, int, int, int) { +} + +/** + * Called whenever a new Collector definition is received from the client. + * Generally, the client will send all of its collectors over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Collector definitions midstream. + */ +void PStatMonitor:: +new_collector(int) { +} + +/** + * Called whenever a new Thread definition is received from the client. + * Generally, the client will send all of its threads over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Thread definitions midstream. + */ +void PStatMonitor:: +new_thread(int) { +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. The use of the PStatFrameData / PStatView objects to report the + * data will facilitate this. + */ +void PStatMonitor:: +new_data(int thread_index, int frame_number) { + const PStatClientData *client_data = get_client_data(); + + // Don't bother to update the thread data until we know at least something + // about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + PStatView &view = get_view(thread_index); + const PStatThreadData *thread_data = view.get_thread_data(); + if (!thread_data->is_empty()) { + int latest = thread_data->get_latest_frame_number(); + if (frame_number == latest) { + view.set_to_frame(thread_data->get_frame(frame_number)); + } + } + } +} + +/** + * Called when a thread should be removed from the list of threads. + */ +void PStatMonitor:: +remove_thread(int) { +} + +/** + * Called whenever the connection to the client has been lost. This is a + * permanent state change. The monitor should update its display to represent + * this, and may choose to close down automatically. + */ +void PStatMonitor:: +lost_connection() { +} + +/** + * If has_idle() returns true, this will be called periodically to allow the + * monitor to update its display or whatever it needs to do. + */ +void PStatMonitor:: +idle() { +} + +/** + * Should be redefined to return true if you want to redefine idle() and + * expect it to be called. + */ +bool PStatMonitor:: +has_idle() { + return false; +} + +/** + * Should be redefined to return true if this monitor class can handle running + * in a sub-thread. + * + * This is not related to the question of whether it can handle multiple + * different PStatThreadDatas; this is strictly a question of whether or not + * the monitor itself wants to run in a sub-thread. + */ +bool PStatMonitor:: +is_thread_safe() { + return false; +} + +/** + * Called when the user guide bars have been changed. + */ +void PStatMonitor:: +user_guide_bars_changed() { +} + +/** + * Opens a new timeline. + */ +PStatGraph *PStatMonitor:: +open_timeline() { + return nullptr; +} + +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + return nullptr; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_flame_graph(int thread_index, int collector_index, int frame_number) { + return nullptr; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *PStatMonitor:: +open_piano_roll(int thread_index) { + return nullptr; +} + +/** + * Writes the client data and open graphs to a datagram. + */ +void PStatMonitor:: +write_datagram(Datagram &dg) const { + dg.add_bool(_client_known); + dg.add_string(_client_hostname); + dg.add_string(_client_progname); + dg.add_int32(_client_pid); + + get_client_data()->write_datagram(dg); + + dg.add_uint32((uint32_t)_colors.size()); + for (const auto &item : _colors) { + dg.add_int32(item.first); + dg.add_float32(item.second[0]); + dg.add_float32(item.second[1]); + dg.add_float32(item.second[2]); + } + + dg.add_uint16(_timelines.size()); + for (PStatGraph *graph : _timelines) { + graph->write_datagram(dg); + } + + dg.add_uint16(_strip_charts.size()); + for (PStatGraph *graph : _strip_charts) { + dg.add_int16(((PStatStripChart *)graph)->get_thread_index()); + dg.add_int16(((PStatStripChart *)graph)->get_collector_index()); + dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level()); + graph->write_datagram(dg); + } + + dg.add_uint16(_flame_graphs.size()); + for (PStatGraph *graph : _flame_graphs) { + dg.add_int16(((PStatFlameGraph *)graph)->get_thread_index()); + dg.add_int16(((PStatFlameGraph *)graph)->get_collector_index()); + graph->write_datagram(dg); + } + + dg.add_uint16(_piano_rolls.size()); + for (PStatGraph *graph : _piano_rolls) { + dg.add_int16(((PStatPianoRoll *)graph)->get_thread_index()); + graph->write_datagram(dg); + } + + // Reserved for future graph type + dg.add_uint16(0); +} + +/** + * Restores the client data and open graphs from a datagram. + */ +void PStatMonitor:: +read_datagram(DatagramIterator &scan) { + _client_known = scan.get_bool(); + _client_hostname = scan.get_string(); + _client_progname = scan.get_string(); + _client_pid = scan.get_int32(); + + PStatClientData *client_data = new PStatClientData; + client_data->read_datagram(scan); + set_client_data(client_data); + + size_t num_colors = scan.get_uint32(); + for (size_t i = 0; i < num_colors; ++i) { + int key = scan.get_int32(); + LRGBColor &color = _colors[key]; + color[0] = scan.get_float32(); + color[1] = scan.get_float32(); + color[2] = scan.get_float32(); + } + + int num_collectors = client_data->get_num_collectors(); + for (int collector_index = 0; collector_index < num_collectors; ++collector_index) { + if (client_data->has_collector(collector_index)) { + new_collector(collector_index); + } + } + + int num_threads = client_data->get_num_threads(); + for (int thread_index = 0; thread_index < num_threads; ++thread_index) { + if (client_data->has_thread(thread_index)) { + const PStatThreadData *thread_data = client_data->get_thread_data(thread_index); + if (!thread_data->is_empty()) { + const PStatFrameData &frame_data = thread_data->get_latest_frame(); + get_view(thread_index).set_to_frame(frame_data); + + int num_collectors = client_data->get_num_toplevel_collectors(); + for (int i = 0; i < num_collectors; ++i) { + int collector_index = client_data->get_toplevel_collector(i); + if (client_data->has_collector(collector_index)) { + get_level_view(collector_index, thread_index).set_to_frame(thread_data->get_latest_frame()); + } + } + } + new_thread(thread_index); + } + } + + PStatGraph *graph; + + size_t num_timelines = scan.get_uint16(); + for (size_t i = 0; i < num_timelines; ++i) { + graph = open_timeline(); + graph->read_datagram(scan); + } + + size_t num_strip_charts = scan.get_uint16(); + for (size_t i = 0; i < num_strip_charts; ++i) { + int thread_index = scan.get_int16(); + int collector_index = scan.get_int16(); + graph = open_strip_chart(thread_index, collector_index, scan.get_bool()); + graph->read_datagram(scan); + } + + size_t num_flame_graphs = scan.get_uint16(); + for (size_t i = 0; i < num_flame_graphs; ++i) { + int thread_index = scan.get_int16(); + int collector_index = scan.get_int16(); + graph = open_flame_graph(thread_index, collector_index); + graph->read_datagram(scan); + } + + size_t num_piano_rolls = scan.get_uint16(); + for (size_t i = 0; i < num_piano_rolls; ++i) { + int thread_index = scan.get_int16(); + graph = open_piano_roll(thread_index); + graph->read_datagram(scan); + } +} diff --git a/pandatool/src/pstatserver/pStatMonitor.h b/pandatool/src/pstatserver/pStatMonitor.h new file mode 100644 index 00000000..d98ce2b9 --- /dev/null +++ b/pandatool/src/pstatserver/pStatMonitor.h @@ -0,0 +1,143 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatMonitor.h + * @author drose + * @date 2000-07-08 + */ + +#ifndef PSTATMONITOR_H +#define PSTATMONITOR_H + +#include "pandatoolbase.h" + +#include "pStatClientData.h" +#include "pStatView.h" + +#include "referenceCount.h" +#include "pointerTo.h" +#include "luse.h" + +#include "pmap.h" + +class PStatCollectorDef; +class PStatGraph; +class PStatServer; + +/** + * This is an abstract class that presents the interface to any number of + * different front-ends for the stats monitor. One of these will be created + * by the PStatMonitor as each client is connected; this class is responsible + * for opening up a new strip-chart graph or whatever is appropriate. It + * defines a number of empty virtual functions that will be called as new data + * becomes available. + */ +class PStatMonitor : public ReferenceCount { +public: + // The following functions are primarily for use by internal classes to set + // up the monitor. + PStatMonitor(PStatServer *server = nullptr); + virtual ~PStatMonitor(); + + void hello_from(const std::string &hostname, const std::string &progname, + int pid); + void bad_version(const std::string &hostname, const std::string &progname, + int pid, + int client_major, int client_minor, + int server_major, int server_minor); + void set_client_data(PStatClientData *client_data); + + bool write(const Filename &fn) const; + bool read(const Filename &fn); + + void open_default_graphs(); + bool save_default_graphs() const; + + // The following functions are for use by user code to determine information + // about the client data available. + bool is_alive() const; + void close(); + + INLINE PStatServer *get_server(); + INLINE const PStatClientData *get_client_data() const; + INLINE std::string get_collector_name(int collector_index); + const LRGBColor &get_collector_color(int collector_index); + void set_collector_color(int collector_index, const LRGBColor &color); + void clear_collector_color(int collector_index); + + INLINE bool is_client_known() const; + INLINE std::string get_client_hostname() const; + INLINE std::string get_client_progname() const; + INLINE int get_client_pid() const; + INLINE bool has_read_filename() const; + INLINE const Filename &get_read_filename() const; + + PStatView &get_view(int thread_index); + PStatView &get_level_view(int collector_index, int thread_index); + + // The following virtual methods may be overridden by a derived monitor + // class to customize behavior. + + virtual std::string get_monitor_name()=0; + + virtual void initialized(); + virtual void got_hello(); + virtual void got_bad_version(int client_major, int client_minor, + int server_major, int server_minor); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + virtual void remove_thread(int thread_index); + + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + + virtual bool is_thread_safe(); + + virtual void user_guide_bars_changed(); + + virtual PStatGraph *open_timeline(); + virtual PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + virtual PStatGraph *open_flame_graph(int thread_index, int collector_index = -1, int frame_number = -1); + virtual PStatGraph *open_piano_roll(int thread_index); + + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan); + +protected: + PStatServer *_server; + +private: + PT(PStatClientData) _client_data; + + bool _client_known; + std::string _client_hostname; + std::string _client_progname; + int _client_pid; + Filename _read_filename; + + typedef pmap Views; + Views _views; + typedef pmap LevelViews; + LevelViews _level_views; + + typedef pmap Colors; + Colors _colors; + +public: + typedef pset Graphs; + Graphs _timelines; + Graphs _strip_charts; + Graphs _flame_graphs; + Graphs _piano_rolls; +}; + +#include "pStatMonitor.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatPianoRoll.I b/pandatool/src/pstatserver/pStatPianoRoll.I new file mode 100644 index 00000000..8f5bfcc8 --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.I @@ -0,0 +1,76 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatPianoRoll.I + * @author drose + * @date 2000-07-18 + */ + +/** + * Returns the particular thread whose data this piano roll reflects. + */ +INLINE int PStatPianoRoll:: +get_thread_index() const { + return _thread_index; +} + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +INLINE void PStatPianoRoll:: +set_horizontal_scale(double time_width) { + if (_time_width != time_width) { + _time_width = time_width; + normal_guide_bars(); + force_redraw(); + } +} + +/** + * Returns the amount of total time the width of the horizontal axis + * represents. + */ +INLINE double PStatPianoRoll:: +get_horizontal_scale() const { + return _time_width; +} + +/** + * Converts a timestamp to a horizontal pixel offset. + */ +INLINE int PStatPianoRoll:: +timestamp_to_pixel(double time) const { + return (int)((double)_xsize * (time - _start_time) / _time_width); +} + +/** + * Converts a horizontal pixel offset to a timestamp. + */ +INLINE double PStatPianoRoll:: +pixel_to_timestamp(int x) const { + return _time_width * (double)x / (double)_xsize + _start_time; +} + +/** + * Converts a value (i.e. a "height" in the strip chart) to a horizontal + * pixel offset. + */ +INLINE int PStatPianoRoll:: +height_to_pixel(double value) const { + return (int)((double)_xsize * value / _time_width); +} + +/** + * Converts a horizontal pixel offset to a value (a "height" in the strip + * chart). + */ +INLINE double PStatPianoRoll:: +pixel_to_height(int x) const { + return _time_width * (double)x / (double)_xsize; +} diff --git a/pandatool/src/pstatserver/pStatPianoRoll.cxx b/pandatool/src/pstatserver/pStatPianoRoll.cxx new file mode 100644 index 00000000..ba95cc3f --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.cxx @@ -0,0 +1,360 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatPianoRoll.cxx + * @author drose + * @date 2000-07-18 + */ + +#include "pStatPianoRoll.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "string_utils.h" +#include "config_pstatclient.h" + +#include + +/** + * This class is used internally to build up the set of color bars defined by + * a frame's worth of data. + */ +PStatPianoRoll::BarBuilder:: +BarBuilder() { + _is_new = true; +} + +/** + * Resets the data in the BarBuilder for a new frame. + */ +void PStatPianoRoll::BarBuilder:: +clear() { + _is_new = false; + _color_bars.clear(); +} + +/** + * Adds a new data point. The first data point for a given collector turns in + * on (starts the bar), the second data point turns it off (ends the bar). + */ +void PStatPianoRoll::BarBuilder:: +add_data_point(double time, bool is_start) { + if (is_start) { + // This is a "start" data point: start the bar. + if (_color_bars.empty() || _color_bars.back()._end >= 0.0) { + ColorBar bar; + bar._start = time; + bar._end = -1.0; + _color_bars.push_back(bar); + } + + } else { + // This is a "stop" data point: end the bar. + if (_color_bars.empty()) { + // A "stop" in the middle of the frame implies a "start" at time 0. + ColorBar bar; + bar._start = 0.0; + bar._end = time; + _color_bars.push_back(bar); + + } else { + _color_bars.back()._end = time; + } + } +} + +/** + * Makes sure that each start-bar data point was matched by a corresponding + * end-bar data point. + */ +void PStatPianoRoll::BarBuilder:: +finish(double time) { + if (!_color_bars.empty() && _color_bars.back()._end < 0.0) { + _color_bars.back()._end = time; + } +} + +/** + * + */ +PStatPianoRoll:: +PStatPianoRoll(PStatMonitor *monitor, int thread_index, int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize), + _thread_index(thread_index) +{ + _time_width = 1.0 / pstats_target_frame_rate; + _start_time = 0.0; + _current_frame = -1; + + // If we already have data, load it in now. + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = + client_data->get_thread_data(thread_index); + if (!thread_data->is_empty()) { + int frame_number = thread_data->get_latest_frame_number(); + compute_page(thread_data->get_frame(frame_number)); + } + } + + _guide_bar_units = GBU_ms | GBU_hz | GBU_show_units; + normal_guide_bars(); + + monitor->_piano_rolls.insert(this); +} + +/** + * + */ +PStatPianoRoll:: +~PStatPianoRoll() { + _monitor->_piano_rolls.erase(this); +} + +/** + * Updates the chart with the latest data. + */ +void PStatPianoRoll:: +update() { + const PStatClientData *client_data = _monitor->get_client_data(); + + // Don't bother to update the thread data until we know at least something + // about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = + client_data->get_thread_data(_thread_index); + if (!thread_data->is_empty()) { + int frame_number = thread_data->get_latest_frame_number(); + if (frame_number != _current_frame) { + compute_page(thread_data->get_frame(frame_number)); + _current_frame = frame_number; + force_redraw(); + } + } + } + + idle(); +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string PStatPianoRoll:: +get_label_tooltip(int collector_index) const { + const PStatClientData *client_data = _monitor->get_client_data(); + if (!client_data->has_collector(collector_index)) { + return std::string(); + } + + return client_data->get_collector_fullname(collector_index); +} + +/** + * Writes the graph state to a datagram. + */ +void PStatPianoRoll:: +write_datagram(Datagram &dg) const { + dg.add_float64(_time_width); + dg.add_float64(_start_time); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatPianoRoll:: +read_datagram(DatagramIterator &scan) { + _time_width = scan.get_float64(); + _start_time = scan.get_float64(); + + PStatGraph::read_datagram(scan); + + _current_frame = -1; + normal_guide_bars(); + update(); +} + +/** + * To be called by the user class when the widget size has changed. This + * updates the chart's internal data and causes it to issue redraw commands to + * reflect the new size. + */ +void PStatPianoRoll:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _xsize = xsize; + _ysize = ysize; + + normal_guide_bars(); + force_redraw(); + } +} + +/** + * To be called by the user class when the whole thing needs to be redrawn for + * some reason. + */ +void PStatPianoRoll:: +force_redraw() { + if (!_labels.empty()) { + begin_draw(); + for (int i = 0; i < (int)_labels.size(); i++) { + int collector_index = _labels[i]; + const ColorBars &bars = _page_data[collector_index]._color_bars; + + begin_row(i); + ColorBars::const_iterator bi; + for (bi = bars.begin(); bi != bars.end(); ++bi) { + const ColorBar &bar = (*bi); + draw_bar(i, timestamp_to_pixel(bar._start), timestamp_to_pixel(bar._end)); + } + end_row(i); + } + end_draw(); + } +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void PStatPianoRoll:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + update_guide_bars(get_xsize() / 100, _time_width); +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any bars in the chart. + */ +void PStatPianoRoll:: +begin_draw() { +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any one row of bars. These bars correspond to the collector whose + * index is get_row_collector(row), and in the color get_row_color(row). + */ +void PStatPianoRoll:: +begin_row(int) { +} + +/** + * Draws a single bar in the chart for the indicated row, in the color + * get_row_color(row), for the indicated horizontal pixel range. + */ +void PStatPianoRoll:: +draw_bar(int, int, int) { +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars for a single row. + */ +void PStatPianoRoll:: +end_row(int) { +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the chart. + */ +void PStatPianoRoll:: +end_draw() { +} + +/** + * Should be overridden by the user class to perform any other updates might + * be necessary after the bars have been redrawn. + */ +void PStatPianoRoll:: +idle() { +} + + +// STL function object for sorting labels in order by the collector's sort +// index, used in compute_page(), below. +class SortCollectorLabels1 { +public: + SortCollectorLabels1(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (int a, int b) const { + return + _client_data->get_collector_def(a)._sort > + _client_data->get_collector_def(b)._sort; + } + const PStatClientData *_client_data; +}; + +/** + * Examines the given frame data and rebuilds the _page_data to match it. + */ +void PStatPianoRoll:: +compute_page(const PStatFrameData &frame_data) { + _start_time = frame_data.get_start(); + + // Clear out the page data and copy it to previous, so we can fill it up + // again and then check to see if we changed the set of bars this frame. + PageData previous; + _page_data.swap(previous); + + int num_events = frame_data.get_num_events(); + for (int i = 0; i < num_events; i++) { + int collector_index = frame_data.get_time_collector(i); + double time = frame_data.get_time(i); + bool is_start = frame_data.is_start(i); + _page_data[collector_index].add_data_point(time, is_start); + } + + // Now check to see if the set of bars has changed. + bool changed_bars = (_page_data.size() != previous.size()); + + if (!changed_bars) { + PageData::const_iterator ai, bi; + ai = _page_data.begin(); + bi = previous.begin(); + while (ai != _page_data.end() && !changed_bars) { + changed_bars = ((*ai).first == (*bi).first); + ++ai; + ++bi; + } + } + + if (changed_bars) { + // If we added or removed some new bars this time, we'll have to update + // our list. + const PStatClientData *client_data = _monitor->get_client_data(); + + _labels.clear(); + PageData::const_iterator pi; + for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) { + int collector_index = (*pi).first; + if (client_data->has_collector(collector_index)) { + _labels.push_back(collector_index); + } + } + + SortCollectorLabels1 sort_labels(client_data); + sort(_labels.begin(), _labels.end(), sort_labels); + + _labels_changed = true; + } + + // Finally, make sure all of the bars are closed. + double time = frame_data.get_end(); + PageData::iterator pi; + for (pi = _page_data.begin(); pi != _page_data.end(); ++pi) { + (*pi).second.finish(time); + } +} diff --git a/pandatool/src/pstatserver/pStatPianoRoll.h b/pandatool/src/pstatserver/pStatPianoRoll.h new file mode 100644 index 00000000..7ba4ac2e --- /dev/null +++ b/pandatool/src/pstatserver/pStatPianoRoll.h @@ -0,0 +1,108 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatPianoRoll.h + * @author drose + * @date 2000-07-18 + */ + +#ifndef PSTATPIANOROLL_H +#define PSTATPIANOROLL_H + +#include "pandatoolbase.h" + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include "luse.h" +#include "vector_int.h" + +#include "pmap.h" + +class PStatFrameData; + +/** + * This is an abstract class that presents the interface for drawing a piano- + * roll type chart: it shows the time spent in each of a number of collectors + * as a horizontal bar of color, with time as the horizontal axis. + * + * This class just manages all the piano-roll logic; the actual nuts and bolts + * of drawing pixels is left to a user-derived class. + */ +class PStatPianoRoll : public PStatGraph { +public: + PStatPianoRoll(PStatMonitor *monitor, int thread_index, + int xsize, int ysize); + virtual ~PStatPianoRoll(); + + void update(); + + INLINE int get_thread_index() const; + + INLINE void set_horizontal_scale(double time_width); + INLINE double get_horizontal_scale() const; + + INLINE int timestamp_to_pixel(double time) const; + INLINE double pixel_to_timestamp(int x) const; + INLINE int height_to_pixel(double value) const; + INLINE double pixel_to_height(int y) const; + + std::string get_label_tooltip(int collector_index) const; + + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + +protected: + void changed_size(int xsize, int ysize); + void force_redraw(); + virtual void normal_guide_bars(); + + virtual void begin_draw(); + virtual void begin_row(int row); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_row(int row); + virtual void end_draw(); + virtual void idle(); + +private: + void compute_page(const PStatFrameData &frame_data); + +protected: + int _thread_index; + +private: + double _time_width; + double _start_time; + + class ColorBar { + public: + double _start; + double _end; + }; + typedef pvector ColorBars; + + class BarBuilder { + public: + BarBuilder(); + void clear(); + void add_data_point(double time, bool is_start); + void finish(double time); + + bool _is_new; + ColorBars _color_bars; + }; + + typedef pmap PageData; + PageData _page_data; + int _current_frame; +}; + +#include "pStatPianoRoll.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatReader.cxx b/pandatool/src/pstatserver/pStatReader.cxx new file mode 100644 index 00000000..7436b92d --- /dev/null +++ b/pandatool/src/pstatserver/pStatReader.cxx @@ -0,0 +1,340 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatReader.cxx + * @author drose + * @date 2000-07-09 + */ + +#include "pStatReader.h" +#include "pStatServer.h" +#include "pStatMonitor.h" + +#include "pStatClientControlMessage.h" +#include "pStatServerControlMessage.h" +#include "pStatFrameData.h" +#include "pStatProperties.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "connectionManager.h" + +/** + * + */ +PStatReader:: +PStatReader(PStatServer *manager, PStatMonitor *monitor) : +#ifdef HAVE_THREADS + ConnectionReader(manager, monitor->is_thread_safe() ? 1 : 0), +#else // HAVE_THREADS + ConnectionReader(manager, 0), +#endif // HAVE_THREADS + _manager(manager), + _monitor(monitor), + _writer(manager, 0) +{ + set_tcp_header_size(4); + _writer.set_tcp_header_size(4); + _udp_port = 0; + _client_data = new PStatClientData(this); + _monitor->set_client_data(_client_data); +} + +/** + * + */ +PStatReader:: +~PStatReader() { + _manager->release_udp_port(_udp_port); +} + +/** + * This will be called by the PStatClientData in response to its close() call. + * It will tell the server to let go of the reader so it can shut down its + * connection. + */ +void PStatReader:: +close() { + _manager->remove_reader(_tcp_connection, this); + lost_connection(); +} + +/** + * This is intended to be called only once, immediately after construction, by + * the PStatListener that created it. It tells the reader about the newly- + * established TCP connection to a client. + */ +void PStatReader:: +set_tcp_connection(Connection *tcp_connection) { + _tcp_connection = tcp_connection; + add_connection(_tcp_connection); + + _udp_port = _manager->get_udp_port(); + _udp_connection = _manager->open_UDP_connection(_udp_port); + while (_udp_connection.is_null()) { + // That UDP port was no good. Try another. + _udp_port = _manager->get_udp_port(); + _udp_connection = _manager->open_UDP_connection(_udp_port); + } + + add_connection(_udp_connection); + + send_hello(); +} + +/** + * This is called by the PStatServer when it detects that the connection has + * been lost. It should clean itself up and shut down nicely. + */ +void PStatReader:: +lost_connection() { + _client_data->_is_alive = false; + _monitor->lost_connection(); + _manager->lost_connection(_monitor); + _client_data.clear(); + + _manager->close_connection(_tcp_connection); + _manager->close_connection(_udp_connection); + _tcp_connection.clear(); + _udp_connection.clear(); +} + +/** + * Called each frame to do what needs to be done for the monitor's user- + * defined idle routines. + */ +void PStatReader:: +idle() { + dequeue_frame_data(); + _monitor->idle(); +} + +/** + * Returns the monitor that this reader serves. + */ +PStatMonitor *PStatReader:: +get_monitor() { + return _monitor; +} + +/** + * Returns the current machine's hostname. + */ +std::string PStatReader:: +get_hostname() { + if (_hostname.empty()) { + _hostname = ConnectionManager::get_host_name(); + if (_hostname.empty()) { + _hostname = "unknown"; + } + } + return _hostname; +} + +/** + * Sends the initial greeting message to the client. + */ +void PStatReader:: +send_hello() { + PStatServerControlMessage message; + message._type = PStatServerControlMessage::T_hello; + message._server_hostname = get_hostname(); + message._server_progname = _monitor->get_monitor_name(); + message._udp_port = _udp_port; + + Datagram datagram; + message.encode(datagram); + _writer.send(datagram, _tcp_connection); +} + +/** + * Called by the net code whenever a new datagram is detected on a either the + * TCP or UDP connection. + */ +void PStatReader:: +receive_datagram(const NetDatagram &datagram) { + Connection *connection = datagram.get_connection(); + + if (connection == _tcp_connection) { + PStatClientControlMessage message; + if (message.decode(datagram, _client_data)) { + handle_client_control_message(message); + + } else if (message._type == PStatClientControlMessage::T_datagram) { + handle_client_udp_data(datagram); + + } else { + nout << "Got unexpected message from client.\n"; + } + + } else if (connection == _udp_connection) { + handle_client_udp_data(datagram); + + } else { + nout << "Got datagram from unexpected socket.\n"; + } +} + +/** + * Called when a control message has been received by the client over the TCP + * connection. + */ +void PStatReader:: +handle_client_control_message(const PStatClientControlMessage &message) { + switch (message._type) { + case PStatClientControlMessage::T_hello: + { + _client_data->set_version(message._major_version, message._minor_version); + int server_major_version = get_current_pstat_major_version(); + int server_minor_version = get_current_pstat_minor_version(); + + if (message._major_version != server_major_version || + (message._major_version == server_major_version && + message._minor_version > server_minor_version)) { + _monitor->bad_version(message._client_hostname, message._client_progname, + message._client_pid, + message._major_version, message._minor_version, + server_major_version, server_minor_version); + _monitor->close(); + } else { + _monitor->hello_from(message._client_hostname, message._client_progname, + message._client_pid); + } + } + break; + + case PStatClientControlMessage::T_define_collectors: + { + for (int i = 0; i < (int)message._collectors.size(); i++) { + _client_data->add_collector(message._collectors[i]); + _monitor->new_collector(message._collectors[i]->_index); + } + } + break; + + case PStatClientControlMessage::T_define_threads: + { + // See if we can clean up old threads, so that we don't clutter up the + // view if we are creating many threads. + for (int thread_index = 0; thread_index < _client_data->get_num_threads(); ++thread_index) { + if (_client_data->has_thread(thread_index) && !_client_data->is_thread_alive(thread_index)) { + PStatThreadData *thread_data = (PStatThreadData *)_client_data->get_thread_data(thread_index); + if (thread_data->prune_history(_client_data->get_latest_time())) { + _client_data->remove_thread(thread_index); + _monitor->remove_thread(thread_index); + } + } + } + + for (int i = 0; i < (int)message._names.size(); i++) { + int thread_index = message._first_thread_index + i; + std::string name = message._names[i]; + _client_data->define_thread(thread_index, name, true); + _monitor->new_thread(thread_index); + } + } + break; + + case PStatClientControlMessage::T_expire_thread: + if (_client_data->has_thread(message._first_thread_index)) { + // Remove the thread right away if it has no recent data. + PStatThreadData *thread_data = (PStatThreadData *)_client_data->get_thread_data(message._first_thread_index); + if (thread_data->prune_history(_client_data->get_latest_time())) { + _client_data->remove_thread(message._first_thread_index); + _monitor->remove_thread(message._first_thread_index); + } else { + // Otherwise, just mark it as expired, and we'll remove it later. + _client_data->expire_thread(message._first_thread_index); + } + } + break; + + default: + nout << "Invalid control message received from client.\n"; + } +} + +/** + * Called when a UDP datagram has been received by the client. This should be + * a single frame's worth of data. + */ +void PStatReader:: +handle_client_udp_data(const Datagram &datagram) { + if (!_monitor->is_client_known()) { + // If we haven't heard a "hello" from the client yet, we don't know what + // version data it will be sending us, so we can't decode the data. + // Chances are good we can't display it sensibly yet anyway. Ignore frame + // data until we get that hello. + return; + } + + DatagramIterator source(datagram); + + if (_client_data->is_at_least(2, 1)) { + // Throw away the zero byte at the beginning. + int initial_byte = source.get_uint8(); + nassertv(initial_byte == 0); + } + + if (!_queued_frame_data.full()) { + FrameData data; + data._thread_index = source.get_uint16(); + data._frame_number = source.get_uint32(); + data._frame_data = new PStatFrameData; + data._frame_data->read_datagram(source, _client_data); + + // Queue up the data till we're ready to handle it in a single-threaded + // way. + _queued_frame_data.push_back(data); + } +} + +/** + * Called during the idle loop to pull out all the frame data that we might + * have read while the threaded reader was running. + */ +void PStatReader:: +dequeue_frame_data() { + if (_queued_frame_data.empty()) { + return; + } + + do { + const FrameData &data = _queued_frame_data.front(); + nassertv(_client_data != nullptr); + + // Check to see if any new collectors have level data. + int num_levels = data._frame_data->get_num_levels(); + for (int i = 0; i < num_levels; i++) { + int collector_index = data._frame_data->get_level_collector(i); + if (!_client_data->get_collector_has_level(collector_index, data._thread_index)) { + // This collector is now reporting level data, and it wasn't before. + _client_data->set_collector_has_level(collector_index, data._thread_index, true); + _monitor->new_collector(collector_index); + } + } + + _client_data->record_new_frame(data._thread_index, + data._frame_number, + data._frame_data); + _monitor->new_data(data._thread_index, data._frame_number); + + _queued_frame_data.pop_front(); + } + while (!_queued_frame_data.empty()); + + // Clean up old threads. + for (int thread_index = 0; thread_index < _client_data->get_num_threads(); ++thread_index) { + if (_client_data->has_thread(thread_index) && !_client_data->is_thread_alive(thread_index)) { + PStatThreadData *thread_data = (PStatThreadData *)_client_data->get_thread_data(thread_index); + if (thread_data->prune_history(_client_data->get_latest_time())) { + _client_data->remove_thread(thread_index); + _monitor->remove_thread(thread_index); + } + } + } +} diff --git a/pandatool/src/pstatserver/pStatReader.h b/pandatool/src/pstatserver/pStatReader.h new file mode 100644 index 00000000..40d1d9b5 --- /dev/null +++ b/pandatool/src/pstatserver/pStatReader.h @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatReader.h + * @author drose + * @date 2000-07-09 + */ + +#ifndef PSTATREADER_H +#define PSTATREADER_H + +#include "pandatoolbase.h" + +#include "pStatClientData.h" +#include "pStatMonitor.h" + +#include "connectionReader.h" +#include "connectionWriter.h" +#include "referenceCount.h" +#include "circBuffer.h" + +class PStatServer; +class PStatMonitor; +class PStatClientControlMessage; +class PStatFrameData; + +// This is the maximum number of frame records that will be queued up from +// this particular client between processing loops. +static const int queued_frame_records = 500; + +/** + * This is the class that does all the work for handling communications from a + * single Panda client. It reads sockets received from the client and boils + * them down into PStatData. + */ +class PStatReader : public ConnectionReader { +public: + PStatReader(PStatServer *manager, PStatMonitor *monitor); + ~PStatReader(); + + void close(); + + void set_tcp_connection(Connection *tcp_connection); + void lost_connection(); + void idle(); + + PStatMonitor *get_monitor(); + +private: + std::string get_hostname(); + void send_hello(); + + virtual void receive_datagram(const NetDatagram &datagram); + + void handle_client_control_message(const PStatClientControlMessage &message); + void handle_client_udp_data(const Datagram &datagram); + void dequeue_frame_data(); + +private: + PStatServer *_manager; + PT(PStatMonitor) _monitor; + ConnectionWriter _writer; + + PT(Connection) _tcp_connection; + PT(Connection) _udp_connection; + int _udp_port; + + PT(PStatClientData) _client_data; + + std::string _hostname; + + class FrameData { + public: + int _thread_index; + int _frame_number; + PStatFrameData *_frame_data; + }; + typedef CircBuffer QueuedFrameData; + QueuedFrameData _queued_frame_data; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatServer.cxx b/pandatool/src/pstatserver/pStatServer.cxx new file mode 100644 index 00000000..095a8706 --- /dev/null +++ b/pandatool/src/pstatserver/pStatServer.cxx @@ -0,0 +1,306 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatServer.cxx + * @author drose + * @date 2000-07-09 + */ + +#include "pStatServer.h" +#include "pStatReader.h" +#include "thread.h" +#include "config_pstatclient.h" + +/** + * + */ +PStatServer:: +PStatServer() { + _listener = new PStatListener(this); + _next_udp_port = 0; +} + +/** + * + */ +PStatServer:: +~PStatServer() { + stop_listening(); + + delete _listener; +} + +/** + * Establishes a port number that the manager will listen on for TCP + * connections. This may be called more than once to listen simulataneously + * on multiple connections, as if that were at all useful. + * + * The default parameter, -1, indicates the use of whatever port number has + * been indicated in the Config file. + * + * This function returns true if the port was successfully opened, or false if + * it could not open the port. + */ +bool PStatServer:: +listen(int port) { + stop_listening(); + + if (port < 0) { + port = pstats_port; + } + + // Now try to listen to the port. + _rendezvous = open_TCP_server_rendezvous(port, 5); + + if (_rendezvous.is_null()) { + // Couldn't get it. + return false; + } + + // Tell the listener about the new port. + _listener->add_connection(_rendezvous); + + if (_next_udp_port == 0) { + _next_udp_port = port + 1; + } + return true; +} + +/** + * Stops listening. + */ +void PStatServer:: +stop_listening() { + if (_rendezvous != nullptr) { + close_connection(_rendezvous); + _rendezvous.clear(); + } +} + +/** + * Checks for any network activity and handles it, if appropriate, and then + * returns. This must be called periodically unless is_thread_safe() is + * redefined to return true on this class and also on all PStatMonitors in + * use. + * + * Alternatively, a program may call main_loop() and yield control of the + * program entirely to the PStatServer. + */ +void PStatServer:: +poll() { + // Delete all the readers that we couldn't delete before. + while (!_lost_readers.empty()) { + PStatReader *reader = _lost_readers.back(); + _lost_readers.pop_back(); + + reader->lost_connection(); + delete reader; + } + while (!_removed_readers.empty()) { + PStatReader *reader = _removed_readers.back(); + _removed_readers.pop_back(); + delete reader; + } + + _listener->poll(); + + Readers::const_iterator ri = _readers.begin(); + while (ri != _readers.end()) { + // Preincrement the iterator, in case we remove it as a result of calling + // poll(). + Readers::const_iterator rnext = ri; + ++rnext; + PStatReader *reader = (*ri).second; + + reader->poll(); + reader->idle(); + + ri = rnext; + } +} + +/** + * An alternative to repeatedly calling poll(), this function yields control + * of the program to the PStatServer. It does not return until the program is + * done. + * + * If interrupt_flag is non-NULL, it is the address of a bool variable that is + * initially false, and may be asynchronously set true to indicate the loop + * should terminate. + */ +void PStatServer:: +main_loop(bool *interrupt_flag) { + while (interrupt_flag == nullptr || !*interrupt_flag) { + poll(); + Thread::sleep(0.1); + } +} + +/** + * Adds the newly-created PStatReader to the list of currently active readers. + */ +void PStatServer:: +add_reader(Connection *connection, PStatReader *reader) { + _readers[connection] = reader; +} + +/** + * Removes the indicated reader. + */ +void PStatServer:: +remove_reader(Connection *connection, PStatReader *reader) { + Readers::iterator ri; + ri = _readers.find(connection); + if (ri == _readers.end() || (*ri).second != reader) { + nout << "Attempt to remove undefined reader.\n"; + } else { + _readers.erase(ri); + _removed_readers.push_back(reader); + } +} + +/** + * Returns a new port number that will probably be free to use as a UDP port. + * The caller should be prepared to accept the possibility that it will be + * already in use by another process, however. + */ +int PStatServer:: +get_udp_port() { + if (_available_udp_ports.empty()) { + return _next_udp_port++; + } + int udp_port = _available_udp_ports.front(); + _available_udp_ports.pop_front(); + return udp_port; +} + +/** + * Indicates that the given UDP port is once again free for use. + */ +void PStatServer:: +release_udp_port(int port) { + _available_udp_ports.push_back(port); +} + +/** + * Returns the current number of user-defined guide bars. + */ +int PStatServer:: +get_num_user_guide_bars() const { + return _user_guide_bars.size(); +} + +/** + * Returns the height of the nth user-defined guide bar. + */ +double PStatServer:: +get_user_guide_bar_height(int n) const { + nassertr(n >= 0 && n < (int)_user_guide_bars.size(), 0.0f); + return _user_guide_bars[n]; +} + +/** + * Adjusts the height of the nth user-defined guide bar. + */ +void PStatServer:: +move_user_guide_bar(int n, double height) { + nassertv(n >= 0 && n < (int)_user_guide_bars.size()); + _user_guide_bars[n] = height; + user_guide_bars_changed(); +} + +/** + * Creates a new user guide bar and returns its index number. + */ +int PStatServer:: +add_user_guide_bar(double height) { + int n = (int)_user_guide_bars.size(); + _user_guide_bars.push_back(height); + user_guide_bars_changed(); + + return n; +} + +/** + * Removes the user guide bar with the indicated index number. All subsequent + * index numbers are adjusted down one. + */ +void PStatServer:: +remove_user_guide_bar(int n) { + nassertv(n >= 0 && n < (int)_user_guide_bars.size()); + _user_guide_bars.erase(_user_guide_bars.begin() + n); + user_guide_bars_changed(); +} + +/** + * Returns the index number of the first user guide bar found whose height is + * within the indicated range, or -1 if no user guide bars fall within the + * range. + */ +int PStatServer:: +find_user_guide_bar(double from_height, double to_height) const { + GuideBars::const_iterator gbi; + for (gbi = _user_guide_bars.begin(); + gbi != _user_guide_bars.end(); + ++gbi) { + double height = (*gbi); + if (height >= from_height && height <= to_height) { + return (int)(gbi - _user_guide_bars.begin()); + } + } + + return -1; +} + +/** + * Called when the user guide bars have been changed. + */ +void PStatServer:: +user_guide_bars_changed() { + Readers::iterator ri; + for (ri = _readers.begin(); ri != _readers.end(); ++ri) { + (*ri).second->get_monitor()->user_guide_bars_changed(); + } +} + +/** + * This should be redefined to return true in derived classes that want to + * deal with multithreaded readers and such. If this returns true, the + * manager will create the listener in its own thread, and thus the + * PStatReader constructors at least will run in a different thread. + * + * This is not related to the question of whether the reader can handle + * multiple different PStatThreadDatas; it's strictly a question of whether + * the readers themselves can run in a separate thread. + */ +bool PStatServer:: +is_thread_safe() { + return false; +} + +/** + * Called when a lost connection is detected by the net code, this should pass + * the word on to the interested parties and clean up gracefully. + */ +void PStatServer:: +connection_reset(const PT(Connection) &connection, bool okflag) { + // Was this a client connection? Tell the reader about it if it was. + close_connection(connection); + + Readers::iterator ri; + ri = _readers.find(connection); + if (ri != _readers.end()) { + PStatReader *reader = (*ri).second; + _readers.erase(ri); + + // Unfortunately, we can't delete the reader right away, because we might + // have been called from a method on the reader! We'll have to save the + // reader pointer and delete it some time later. + _lost_readers.push_back(reader); + } +} diff --git a/pandatool/src/pstatserver/pStatServer.h b/pandatool/src/pstatserver/pStatServer.h new file mode 100644 index 00000000..b4d53ed1 --- /dev/null +++ b/pandatool/src/pstatserver/pStatServer.h @@ -0,0 +1,89 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatServer.h + * @author drose + * @date 2000-07-09 + */ + +#ifndef PSTATSERVER_H +#define PSTATSERVER_H + +#include "pandatoolbase.h" +#include "pStatListener.h" +#include "connectionManager.h" +#include "vector_stdfloat.h" +#include "pmap.h" +#include "pdeque.h" + +class PStatReader; + +/** + * The overall manager of the network connections. This class gets the ball + * rolling; to use this package, you need to derive from this and define + * make_monitor() to allocate and return a PStatMonitor of the suitable type. + * + * Then create just one PStatServer object and call listen() with the port(s) + * you would like to listen on. It will automatically create PStatMonitors as + * connections are established and mark the connections closed as they are + * lost. + */ +class PStatServer : public ConnectionManager { +public: + PStatServer(); + ~PStatServer(); + + bool listen(int port = -1); + void stop_listening(); + + void poll(); + void main_loop(bool *interrupt_flag = nullptr); + + virtual PStatMonitor *make_monitor(const NetAddress &address)=0; + virtual void lost_connection(PStatMonitor *monitor) {} + + void add_reader(Connection *connection, PStatReader *reader); + void remove_reader(Connection *connection, PStatReader *reader); + + int get_udp_port(); + void release_udp_port(int port); + + int get_num_user_guide_bars() const; + double get_user_guide_bar_height(int n) const; + void move_user_guide_bar(int n, double height); + int add_user_guide_bar(double height); + void remove_user_guide_bar(int n); + int find_user_guide_bar(double from_height, double to_height) const; + + virtual bool is_thread_safe(); + +protected: + virtual void connection_reset(const PT(Connection) &connection, + bool okflag); + +private: + void user_guide_bars_changed(); + + PStatListener *_listener; + PT(Connection) _rendezvous; + + typedef pmap Readers; + Readers _readers; + typedef pvector LostReaders; + LostReaders _lost_readers; + LostReaders _removed_readers; + + typedef pdeque Ports; + Ports _available_udp_ports; + int _next_udp_port; + + typedef vector_stdfloat GuideBars; + GuideBars _user_guide_bars; +}; + +#endif diff --git a/pandatool/src/pstatserver/pStatStripChart.I b/pandatool/src/pstatserver/pStatStripChart.I new file mode 100644 index 00000000..3e924a1b --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.I @@ -0,0 +1,186 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatStripChart.I + * @author drose + * @date 2000-07-15 + */ + +/** + * Returns the View this chart represents. + */ +INLINE PStatView &PStatStripChart:: +get_view() const { + return _view; +} + +/** + * Returns the particular thread whose data this strip chart reflects. + */ +INLINE int PStatStripChart:: +get_thread_index() const { + return _thread_index; +} + +/** + * Returns the particular collector whose data this strip chart reflects. + */ +INLINE int PStatStripChart:: +get_collector_index() const { + return _collector_index; +} + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +INLINE void PStatStripChart:: +set_horizontal_scale(double time_width) { + if (_time_width != time_width) { + if (_scroll_mode) { + _start_time += _time_width - time_width; + } else { + force_reset(); + } + _time_width = time_width; + } +} + +/** + * Returns the amount of total time the width of the horizontal axis + * represents. + */ +INLINE double PStatStripChart:: +get_horizontal_scale() const { + return _time_width; +} + +/** + * Changes the value the height of the vertical axis represents. This may + * force a redraw. + */ +INLINE void PStatStripChart:: +set_vertical_scale(double value_height) { + if (_value_height != value_height) { + _value_height = value_height; + normal_guide_bars(); + force_redraw(); + } +} + +/** + * Returns total value the height of the vertical axis represents. + */ +INLINE double PStatStripChart:: +get_vertical_scale() const { + return _value_height; +} + +/** + * Changes the scroll_mode flag. When true, the strip chart will update + * itself by scrolling to the left; when false, the strip chart will wrap + * around at the right and restart at the left end without scrolling. + */ +INLINE void PStatStripChart:: +set_scroll_mode(bool scroll_mode) { + if (_scroll_mode != scroll_mode) { + _scroll_mode = scroll_mode; + _first_data = true; + } +} + +/** + * Returns the current state of the scroll_mode flag. When true, the strip + * chart will update itself by scrolling to the left; when false, the strip + * chart will wrap around at the right and restart at the left end without + * scrolling. + */ +INLINE bool PStatStripChart:: +get_scroll_mode() const { + return _scroll_mode; +} + +/** + * Changes the average_mode flag. When true, the strip chart will average out + * the color values over pstats_average_time seconds, which hides spikes and + * makes the overall trends easier to read. When false, the strip chart shows + * the actual data as it is happening. + */ +INLINE void PStatStripChart:: +set_average_mode(bool average_mode) { + if (_average_mode != average_mode) { + _average_mode = average_mode; + force_redraw(); + } +} + +/** + * Returns the current state of the average_mode flag. When true, the strip + * chart will average out the color values over pstats_average_time seconds, + * which hides spikes and makes the overall trends easier to read. When + * false, the strip chart shows the actual data as it is happening. + */ +INLINE bool PStatStripChart:: +get_average_mode() const { + return _average_mode; +} + +/** + * Converts a timestamp to a horizontal pixel offset. + */ +INLINE int PStatStripChart:: +timestamp_to_pixel(double time) const { + return (int)((double)get_xsize() * (time - _start_time) / _time_width); +} + +/** + * Converts a horizontal pixel offset to a timestamp. + */ +INLINE double PStatStripChart:: +pixel_to_timestamp(int x) const { + return _time_width * (double)x / (double)get_xsize() + _start_time; +} + +/** + * Converts a value (i.e. a "height" in the strip chart) to a vertical pixel + * offset. + */ +INLINE int PStatStripChart:: +height_to_pixel(double value) const { + return get_ysize() - (int)((double)get_ysize() * value / _value_height); +} + +/** + * Converts a vertical pixel offset to a value (a "height" in the strip + * chart). + */ +INLINE double PStatStripChart:: +pixel_to_height(int x) const { + return _value_height * (double)(get_ysize() - x) / (double)get_ysize(); +} + +/** + * Returns true if get_title_text() has never yet returned an answer, false if + * it has. + */ +INLINE bool PStatStripChart:: +is_title_unknown() const { + return _title_unknown; +} + +/** + * Returns true if the indicated collector appears anywhere on the chart at + * the current time, false otherwise. + */ +INLINE bool PStatStripChart:: +is_label_used(int collector_index) const { + if (collector_index < (int)_label_usage.size()) { + return _label_usage[collector_index] > 0; + } + return false; +} diff --git a/pandatool/src/pstatserver/pStatStripChart.cxx b/pandatool/src/pstatserver/pStatStripChart.cxx new file mode 100644 index 00000000..d961b7c0 --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.cxx @@ -0,0 +1,1128 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatStripChart.cxx + * @author drose + * @date 2000-07-15 + */ + +#include "pStatStripChart.h" +#include "pStatClientData.h" +#include "pStatMonitor.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "string_utils.h" +#include "config_pstatclient.h" + +#include + +using std::max; +using std::min; + +/** + * + */ +PStatStripChart:: +PStatStripChart(PStatMonitor *monitor, + int thread_index, int collector_index, bool show_level, + int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize), + _thread_index(thread_index), + _view(show_level ? monitor->get_level_view(0, thread_index) : monitor->get_view(thread_index)), + _collector_index(collector_index) +{ + _scroll_mode = pstats_scroll_mode; + _average_mode = false; + + _next_frame = 0; + _first_data = true; + _cursor_pixel = 0; + + _time_width = 20.0; + _value_height = 1.0/10.0; + _start_time = 0.0; + + _level_index = -1; + _title_unknown = true; + + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->has_collector(_collector_index)) { + const PStatCollectorDef &def = client_data->get_collector_def(_collector_index); + _unit_name = def._level_units; + } + + set_auto_vertical_scale(); + + monitor->_strip_charts.insert(this); +} + +/** + * + */ +PStatStripChart:: +~PStatStripChart() { + _monitor->_strip_charts.erase(this); +} + +/** + * Indicates that new data has become available. + */ +void PStatStripChart:: +new_data(int frame_number) { + // If the new frame is older than the last one we've drawn, we'll need to + // back up and redraw it. This can happen when frames arrive out of order + // from the client. + _next_frame = min(frame_number, _next_frame); +} + +/** + * Updates the chart with the latest data. + */ +void PStatStripChart:: +update() { + const PStatClientData *client_data = get_monitor()->get_client_data(); + + // Don't bother to update the thread data until we know at least something + // about the collectors and threads. + if (client_data->get_num_collectors() != 0 && + client_data->get_num_threads() != 0) { + const PStatThreadData *thread_data = _view.get_thread_data(); + if (!thread_data->is_empty()) { + int latest = thread_data->get_latest_frame_number(); + + if (latest > _next_frame) { + draw_frames(_next_frame, latest); + } + _next_frame = latest; + + // Clean out the old data. + double oldest_time = + thread_data->get_frame(latest).get_start() - _time_width; + + Data::iterator di; + di = _data.begin(); + while (di != _data.end() && + thread_data->get_frame((*di).first).get_start() < oldest_time) { + dec_label_usage((*di).second); + _data.erase(di); + di = _data.begin(); + } + } + } + + if (_level_index != _view.get_level_index()) { + update_labels(); + } + + idle(); +} + +/** + * Returns true if the chart has seen its first data appear on it, false if it + * is still a virgin chart. + */ +bool PStatStripChart:: +first_data() const { + return _first_data; +} + +/** + * Changes the collector represented by this strip chart. This may force a + * redraw. + */ +void PStatStripChart:: +set_collector_index(int collector_index) { + if (_collector_index != collector_index) { + _collector_index = collector_index; + _title_unknown = true; + _data.clear(); + clear_label_usage(); + set_auto_vertical_scale(); + force_redraw(); + update_labels(); + } +} + +/** + * Sets the vertical scale according to the suggested scale of the base + * collector, if any, or to center the target frame rate bar otherwise. + */ +void PStatStripChart:: +set_default_vertical_scale() { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->has_collector(_collector_index)) { + const PStatCollectorDef &def = + client_data->get_collector_def(_collector_index); + if (def._suggested_scale != 0.0) { + set_vertical_scale(def._suggested_scale); + return; + } + } + + set_vertical_scale(2.0 / get_target_frame_rate()); +} + +/** + * Sets the vertical scale to make all the data visible. + */ +void PStatStripChart:: +set_auto_vertical_scale() { + const PStatThreadData *thread_data = _view.get_thread_data(); + + // Calculate the median value. + std::vector values; + + if (thread_data != nullptr && !thread_data->is_empty()) { + // Find the oldest visible frame. + double start_time = pixel_to_timestamp(0); + int oldest_frame = std::max( + thread_data->get_frame_number_at_time(start_time), + thread_data->get_oldest_frame_number()); + int latest_frame = thread_data->get_latest_frame_number(); + + for (int frame_number = oldest_frame; frame_number <= latest_frame; ++frame_number) { + if (thread_data->has_frame(frame_number)) { + const FrameData &frame = get_frame_data(frame_number); + values.push_back(frame._net_value); + } + } + } + + if (values.empty()) { + set_default_vertical_scale(); + return; + } + + double median; + size_t half = values.size() / 2; + if (values.size() % 2 == 0) { + std::sort(values.begin(), values.end()); + median = (values[half] + values[half + 1]) / 2.0; + } else { + std::nth_element(values.begin(), values.begin() + half, values.end()); + median = values[half]; + } + + if (median > 0.0) { + // Take 1.5 times the median value as the vertical scale. + set_vertical_scale(median * 1.5); + } else { + set_default_vertical_scale(); + } +} + +/** + * Return the collector index associated with the particular band of color at + * the indicated pixel location, or -1 if no band of color was at the pixel. + */ +int PStatStripChart:: +get_collector_under_pixel(int xpoint, int ypoint) { + // First, we need to know what frame it was; to know that, we need to + // determine the time corresponding to the x pixel. + double time = pixel_to_timestamp(xpoint); + + // Now use that time to determine the frame. + const PStatThreadData *thread_data = _view.get_thread_data(); + + if (time < thread_data->get_oldest_time()) { + return -1; + } + + // And now we can determine which collector within the frame, based on the + // value height. + if (_average_mode) { + double start_time = pixel_to_timestamp(xpoint); + int then_i = thread_data->get_frame_number_at_time(start_time - pstats_average_time); + int now_i = thread_data->get_frame_number_at_time(start_time, then_i); + + FrameData fdata; + compute_average_pixel_data(fdata, then_i, now_i, start_time); + double overall_value = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + overall_value += cd._net_value; + y = height_to_pixel(overall_value); + if (y <= ypoint) { + return cd._collector_index; + } + } + + } else { + int frame_number = thread_data->get_frame_number_at_time(time); + const FrameData &fdata = get_frame_data(frame_number); + double overall_value = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + overall_value += cd._net_value; + y = height_to_pixel(overall_value); + if (y <= ypoint) { + return cd._collector_index; + } + } + } + + return -1; +} + +/** + * Returns the text suitable for the title label on the top line. + */ +std::string PStatStripChart:: +get_title_text() { + std::string text; + + _title_unknown = false; + + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data->has_collector(_collector_index)) { + text = client_data->get_collector_fullname(_collector_index); + const PStatCollectorDef &def = client_data->get_collector_def(_collector_index); + if (_view.get_show_level()) { + if (!def._level_units.empty()) { + text += " (" + def._level_units + ")"; + } + } else { + text += " time"; + } + } else { + _title_unknown = true; + } + + if (_thread_index != 0) { + if (client_data->has_thread(_thread_index)) { + text += " (" + client_data->get_thread_name(_thread_index) + " thread)"; + } else { + _title_unknown = true; + } + } + + return text; +} + +/** + * Returns the text suitable for the total label above the graph. + */ +std::string PStatStripChart:: +get_total_text() { + std::string text = format_number(get_average_net_value(), get_guide_bar_units(), get_guide_bar_unit_name()); + if (get_collector_index() != 0 && !_view.get_show_level()) { + const PStatViewLevel *level = _view.get_level(get_collector_index()); + if (level != nullptr && level->get_count() > 0) { + text += " / " + format_string(level->get_count()) + "x"; + } + } + return text; +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string PStatStripChart:: +get_label_tooltip(int collector_index) const { + const PStatClientData *client_data = _monitor->get_client_data(); + if (!client_data->has_collector(collector_index)) { + return std::string(); + } + + const PStatThreadData *thread_data = _view.get_thread_data(); + + std::ostringstream text; + text << client_data->get_collector_fullname(collector_index); + + double value; + if (collector_index == _collector_index) { + value = get_average_net_value(); + } + else { + int now_i, then_i; + if (!thread_data->get_elapsed_frames(then_i, now_i)) { + return text.str(); + } + double now = _time_width + _start_time; + double then = now - pstats_average_time; + + double net_value = 0.0; + double net_time = 0.0; + + // We start with just the portion of frame then_i that actually does fall + // within our "then to now" window (usually some portion of it will). + const PStatFrameData &frame_data = thread_data->get_frame(then_i); + if (frame_data.get_end() > then) { + double this_time = (frame_data.get_end() - then); + for (const ColorData &cd : get_frame_data(then_i)) { + if (cd._collector_index == collector_index) { + net_value += cd._net_value * this_time; + net_time += this_time; + break; + } + } + } + // Then we get all of each of the remaining frames. + for (int frame_number = then_i + 1; + frame_number <= now_i; + frame_number++) { + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + for (const ColorData &cd : get_frame_data(frame_number)) { + if (cd._collector_index == collector_index) { + double this_time = frame_data.get_net_time(); + net_value += cd._net_value * this_time; + net_time += this_time; + break; + } + } + } + + if (net_time == 0) { + return text.str(); + } + value = net_value / net_time; + } + + text << " (" << format_number(value, get_guide_bar_units(), get_guide_bar_unit_name()); + + if (collector_index != 0) { + const FrameData &frame = get_frame_data(thread_data->get_latest_frame_number()); + + for (const ColorData &cd : frame) { + if (cd._collector_index == collector_index) { + if (cd._count > 0) { + text << " / " << cd._count << "x"; + } + break; + } + } + } + + text << ")"; + return text.str(); +} + +/** + * Writes the graph state to a datagram. + */ +void PStatStripChart:: +write_datagram(Datagram &dg) const { + dg.add_bool(_scroll_mode); + dg.add_bool(_average_mode); + dg.add_float64(_time_width); + dg.add_float64(_start_time); + dg.add_float64(_value_height); + + // Not really necessary, we reconstructed this from the client data. + //for (const auto &item : _data) { + // dg.add_int32(item.first); + // dg.add_uint32(item.second.size()); + // + // for (const ColorData &cd : item.second) { + // dg.add_uint16(cd._collector_index); + // dg.add_uint16(cd._i); + // dg.add_float64(cd._net_value); + // } + //} + //dg.add_int32(-1); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatStripChart:: +read_datagram(DatagramIterator &scan) { + _next_frame = 0; + force_reset(); + + _scroll_mode = scan.get_bool(); + _average_mode = scan.get_bool(); + _time_width = scan.get_float64(); + _start_time = scan.get_float64(); + _value_height = scan.get_float64(); + + //int key; + //while ((key = scan.get_int32()) != -1) { + // FrameData &fdata = _data[key]; + // fdata.resize(scan.get_uint32()); + // + // for (ColorData &cd : fdata) { + // cd._collector_index = scan.get_uint16(); + // cd._i = scan.get_uint16(); + // cd._net_value = scan.get_float64(); + // } + //} + + PStatGraph::read_datagram(scan); + + normal_guide_bars(); + update(); +} + +/** + * Adds the data from additional into the data from fdata, after applying the + * scale weight. + */ +void PStatStripChart:: +accumulate_frame_data(FrameData &fdata, const FrameData &additional, + double weight) { + FrameData::iterator ai; + FrameData::const_iterator bi; + + ai = fdata.begin(); + bi = additional.begin(); + + FrameData result; + + if (fdata.size() == additional.size()) { + // Start out assuming that fdata and additional contain exactly the same + // set of collectors. If we discover otherwise, we'll have to bail at + // that point. + while (ai != fdata.end() && + (*ai)._collector_index == (*bi)._collector_index) { + (*ai)._net_value += ((*bi)._net_value * weight); + ++ai; + ++bi; + } + + if (ai == fdata.end()) { + // If we successfully reached the end of the list, great! We're done + // without any merging. + return; + } + + // Otherwise, the two lists weren't identical. In that case, copy the + // accumulated data so far and continue from this point with the full- + // blown merge. + result.reserve(max(fdata.size(), additional.size())); + FrameData::const_iterator ci; + for (ci = fdata.begin(); ci != ai; ++ci) { + result.push_back(*ci); + } + + } else { + // If the two lists had different lengths, clearly they aren't identical. + result.reserve(max(fdata.size(), additional.size())); + } + + while (ai != fdata.end() && bi != additional.end()) { + if ((*ai)._i < (*bi)._i) { + // Here's a data value that's in data, but not in additional. + result.push_back(*ai); + ++ai; + + } else if ((*bi)._i < (*ai)._i) { + // Here's a data value that's in additional, but not in data. + ColorData scaled; + scaled._collector_index = (*bi)._collector_index; + scaled._i = (*bi)._i; + scaled._count = 0; + scaled._net_value = (*bi)._net_value * weight; + result.push_back(scaled); + ++bi; + + } else { + // Here's a data value that's in both. + ColorData combined; + combined._collector_index = (*ai)._collector_index; + combined._i = (*bi)._i; + combined._count = 0; + combined._net_value = (*ai)._net_value + (*bi)._net_value * weight; + result.push_back(combined); + ++ai; + ++bi; + } + } + + while (ai != fdata.end()) { + // Here's a data value that's in data, but not in additional. + result.push_back(*ai); + ++ai; + } + + while (bi != additional.end()) { + // Here's a data value that's in additional, but not in data. + ColorData scaled; + scaled._collector_index = (*bi)._collector_index; + scaled._i = (*bi)._i; + scaled._count = 0; + scaled._net_value = (*bi)._net_value * weight; + result.push_back(scaled); + ++bi; + } + + fdata.swap(result); +} + +/** + * Applies the indicated scale to all collector values in data. + */ +void PStatStripChart:: +scale_frame_data(FrameData &fdata, double factor) { + FrameData::iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + (*fi)._net_value *= factor; + } +} + + +/** + * Returns the cached FrameData associated with the given frame number. This + * describes the lengths of the color bands for a single vertical stripe in + * the chart. + */ +const PStatStripChart::FrameData &PStatStripChart:: +get_frame_data(int frame_number) const { + Data::const_iterator di; + di = _data.find(frame_number); + if (di != _data.end()) { + return (*di).second; + } + + const PStatThreadData *thread_data = _view.get_thread_data(); + //assert(thread_data->has_frame(frame_number)); + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + _view.set_to_frame(frame_data); + + FrameData &fdata = _data[frame_number]; + fdata._start = frame_data.get_start(); + fdata._end = frame_data.get_end(); + fdata._net_time = frame_data.get_net_time(); + + double net_value = 0.0; + + const PStatViewLevel *level = _view.get_level(_collector_index); + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + const PStatViewLevel *child = level->get_child(i); + ColorData cd; + cd._collector_index = (unsigned short)child->get_collector(); + cd._i = (unsigned short)i; + cd._count = child->get_count(); + cd._net_value = child->get_net_value(); + if (cd._net_value != 0.0) { + fdata.push_back(cd); + net_value += cd._net_value; + } + } + + // Also, there might be some value in the overall Collector that wasn't + // included in all of the children. + ColorData cd; + cd._collector_index = (unsigned short)level->get_collector(); + cd._i = (unsigned short)num_children; + cd._count = level->get_count(); + cd._net_value = level->get_value_alone(); + if (cd._net_value > 0.0) { + fdata.push_back(cd); + net_value += cd._net_value; + } + + fdata._net_value = net_value; + + ((PStatStripChart *)this)->inc_label_usage(fdata); + + return fdata; +} + +/** + * Fills the indicated FrameData structure with the color data for the + * indicated pixel, averaged over the past pstats_average_time seconds. + * + * now is the timestamp for which we are computing the data; then_i and now_i + * are the frame numbers that bound (now - pstats_average_time) and now. At + * function initialization time, these should be at or below the actual + * values; they will be incremented as needed by this function. This allows + * the function to be called repeatedly for successive pixels. + */ +void PStatStripChart:: +compute_average_pixel_data(PStatStripChart::FrameData &result, + int &then_i, int &now_i, double now) { + result.clear(); + + const PStatThreadData *thread_data = _view.get_thread_data(); + if (thread_data->is_empty() || thread_data->get_oldest_time() > now) { + // No data. + return; + } + + double then = now - pstats_average_time; + then_i = thread_data->get_frame_number_after(then, then_i); + now_i = thread_data->get_frame_number_after(now, now_i); + + const FrameData *fdata = &get_frame_data(then_i); + then = max(then, fdata->_start); + double then_end = fdata->_end; + + // Sum up a weighted average of all of the individual frames we pass. + + // We start with just the portion of frame then_i that actually does fall + // within our "then to now" window. + accumulate_frame_data(result, *fdata, then_end - then); + double last = then_end; + + // Then we get all of each of the middle frames. + double weight = 0.0; + for (int frame_number = then_i + 1; + frame_number < now_i; + frame_number++) { + if (thread_data->has_frame(frame_number)) { + if (weight > 0.0) { + accumulate_frame_data(result, *fdata, weight); + weight = 0.0; + } + fdata = &get_frame_data(frame_number); + } + weight += fdata->_end - last; + last = fdata->_end; + } + + if (weight > 0.0) { + accumulate_frame_data(result, *fdata, weight); + } + + // And finally, we get the remainder as now_i. + if (last <= now) { + accumulate_frame_data(result, get_frame_data(now_i), now - last); + } + + scale_frame_data(result, 1.0f / (now - then)); +} + +/** + * Computes the average value of the chart's collector over the past + * pstats_average_time number of seconds. + */ +double PStatStripChart:: +get_average_net_value() const { + const PStatThreadData *thread_data = _view.get_thread_data(); + int now_i, then_i; + if (!thread_data->get_elapsed_frames(then_i, now_i)) { + return 0.0; + } + double now = _time_width + _start_time; + double then = now - pstats_average_time; + + int num_frames = now_i - then_i + 1; + + if (_collector_index == 0 && !_view.get_show_level()) { + // If we're showing the time for the whole frame, compute this from the + // total elapsed time, rather than summing up individual frames. This is + // more accurate and exactly matches what is reported by + // thread_data->get_frame_rate(). + + const PStatFrameData &now_frame_data = thread_data->get_frame(now_i); + const PStatFrameData &then_frame_data = thread_data->get_frame(then_i); + double now = now_frame_data.get_end(); + double elapsed_time = (now - then_frame_data.get_start()); + return elapsed_time / (double)num_frames; + + } else { + // On the other hand, if we're showing the time for some sub-frame, we + // have to do it the less-accurate way of summing up individual frames, + // which might introduce errors if we are missing data for some frames, + // but what can you do? + + const PStatThreadData *thread_data = _view.get_thread_data(); + + double net_value = 0.0; + double net_time = 0.0; + + // We start with just the portion of frame then_i that actually does fall + // within our "then to now" window (usually some portion of it will). + const PStatFrameData &frame_data = thread_data->get_frame(then_i); + const FrameData *frame = &get_frame_data(then_i); + if (frame_data.get_end() > then) { + double this_time = (frame->_end - then); + net_value += frame->_net_value * this_time; + net_time += this_time; + } + // Then we get all of each of the remaining frames. + for (int frame_number = then_i + 1; + frame_number <= now_i; + frame_number++) { + if (thread_data->has_frame(frame_number)) { + frame = &get_frame_data(frame_number); + } + double this_time = frame->_net_time; + net_value += frame->_net_value * this_time; + net_time += this_time; + } + + return net_value / net_time; + } +} + +/** + * To be called by the user class when the widget size has changed. This + * updates the chart's internal data and causes it to issue redraw commands to + * reflect the new size. + */ +void PStatStripChart:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _xsize = xsize; + _ysize = ysize; + if (_xsize > 0 && _ysize > 0) { + _cursor_pixel = xsize * _cursor_pixel / _xsize; + + if (!_first_data) { + if (_scroll_mode) { + draw_pixels(0, _xsize); + + } else { + // Redraw the stats that were there before. + double old_start_time = _start_time; + + // Back up a bit to draw the stuff to the right of the cursor. + _start_time -= _time_width; + draw_pixels(_cursor_pixel, _xsize); + + // Now draw the stuff to the left of the cursor. + _start_time = old_start_time; + draw_pixels(0, _cursor_pixel); + } + } + } + } +} + +/** + * To be called by the user class when the whole thing needs to be redrawn for + * some reason. + */ +void PStatStripChart:: +force_redraw() { + if (!_first_data) { + draw_pixels(0, _xsize); + } +} + +/** + * To be called by the user class to cause the chart to reset to empty and + * start filling again. + */ +void PStatStripChart:: +force_reset() { + clear_region(); + _first_data = true; +} + + +/** + * Should be overridden by the user class to wipe out the entire strip chart + * region. + */ +void PStatStripChart:: +clear_region() { +} + +/** + * Should be overridden by the user class to copy a region of the chart from + * one part of the chart to another. This is used to implement scrolling. + */ +void PStatStripChart:: +copy_region(int, int, int) { +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any color bars in the strip chart; it gives the pixel range that's + * about to be redrawn. + */ +void PStatStripChart:: +begin_draw(int, int) { +} + +/** + * Should be overridden by the user class to draw a single vertical slice in + * the strip chart at the indicated pixel, with the data for the indicated + * frame. + */ +void PStatStripChart:: +draw_slice(int, int, const PStatStripChart::FrameData &fdata) { +} + +/** + * This is similar to draw_slice(), except it should draw a vertical line of + * the background color to represent a portion of the chart that has no data. + */ +void PStatStripChart:: +draw_empty(int, int) { +} + +/** + * This is similar to draw_slice(), except that it should draw the black + * vertical stripe that represents the current position when not in scrolling + * mode. + */ +void PStatStripChart:: +draw_cursor(int) { +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the strip chart; it gives the pixel range + * that was just redrawn. + */ +void PStatStripChart:: +end_draw(int, int) { +} + +/** + * Should be overridden by the user class to perform any other updates might + * be necessary after the color bars have been redrawn. For instance, it + * could check the state of _labels_changed, and redraw the labels if it is + * true. + */ +void PStatStripChart:: +idle() { +} + +// STL function object for sorting labels in order by the collector's sort +// index, used in update_labels(), below. +class SortCollectorLabels2 { +public: + SortCollectorLabels2(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (int a, int b) const { + return + _client_data->get_collector_def(a)._sort > + _client_data->get_collector_def(b)._sort; + } + const PStatClientData *_client_data; +}; + +/** + * Resets the list of labels. + */ +void PStatStripChart:: +update_labels() { + const PStatViewLevel *level = _view.get_level(_collector_index); + _labels.clear(); + + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + const PStatViewLevel *child = level->get_child(i); + int collector_index = child->get_collector(); + if (is_label_used(collector_index)) { + _labels.push_back(collector_index); + } + } + + SortCollectorLabels2 sort_labels(get_monitor()->get_client_data()); + sort(_labels.begin(), _labels.end(), sort_labels); + + int collector_index = level->get_collector(); + _labels.push_back(collector_index); + + _labels_changed = true; + _level_index = _view.get_level_index(); +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void PStatStripChart:: +normal_guide_bars() { + update_guide_bars(4, _value_height); +} + + +/** + * Draws the levels for the indicated frame range. + */ +void PStatStripChart:: +draw_frames(int first_frame, int last_frame) { + const PStatThreadData *thread_data = _view.get_thread_data(); + + last_frame = min(last_frame, thread_data->get_latest_frame_number()); + + if (_first_data) { + if (_scroll_mode) { + _start_time = + thread_data->get_frame(last_frame).get_start() - _time_width; + } else { + _start_time = thread_data->get_frame(first_frame).get_start(); + _cursor_pixel = 0; + } + } + + int first_pixel; + if (thread_data->has_frame(first_frame)) { + first_pixel = + timestamp_to_pixel(thread_data->get_frame(first_frame).get_start()); + } else { + first_pixel = 0; + } + + int last_pixel = + timestamp_to_pixel(thread_data->get_frame(last_frame).get_start()); + + if (_first_data && !_scroll_mode) { + first_pixel = min(_cursor_pixel, first_pixel); + } + _first_data = false; + + if (last_pixel - first_pixel >= _xsize) { + // If we're drawing the whole thing all in this one swoop, just start + // over. + _start_time = thread_data->get_frame(last_frame).get_start() - _time_width; + first_pixel = 0; + last_pixel = _xsize; + } + + if (last_pixel <= _xsize) { + // It all fits in one block. + _cursor_pixel = last_pixel; + draw_pixels(first_pixel, last_pixel); + + } else { + if (_scroll_mode) { + // In scrolling mode, slide the world back. + int slide_pixels = last_pixel - _xsize; + // This is really slow on macOS, just redraw instead +#ifdef __APPLE__ + draw_pixels(0, first_pixel - slide_pixels); +#else + copy_region(slide_pixels, first_pixel, 0); +#endif + first_pixel -= slide_pixels; + last_pixel -= slide_pixels; + _start_time += (double)slide_pixels / (double)_xsize * _time_width; + draw_pixels(first_pixel, last_pixel); + + } else { + // In wrapping mode, do it in two blocks. + _cursor_pixel = -1; + draw_pixels(first_pixel, _xsize); + _start_time = pixel_to_timestamp(_xsize); + last_pixel -= _xsize; + _cursor_pixel = last_pixel; + draw_pixels(0, last_pixel); + } + } +} + +/** + * Draws the levels for the indicated pixel range. + */ +void PStatStripChart:: +draw_pixels(int first_pixel, int last_pixel) { + begin_draw(first_pixel, last_pixel); + const PStatThreadData *thread_data = _view.get_thread_data(); + + if (_average_mode && !thread_data->is_empty()) { + // In average mode, we have to calculate the average value for each pixel. + double start_time = pixel_to_timestamp(first_pixel); + int then_i = thread_data->get_frame_number_at_time(start_time - pstats_average_time); + int now_i = thread_data->get_frame_number_at_time(start_time, then_i); + for (int x = first_pixel; x <= last_pixel; x++) { + if (x == _cursor_pixel && !_scroll_mode) { + draw_cursor(x); + } else { + FrameData fdata; + compute_average_pixel_data(fdata, then_i, now_i, pixel_to_timestamp(x)); + draw_slice(x, 1, fdata); + } + } + + } else { + // When average mode is false, we are in frame mode; just show the actual + // frame data. + int frame_number = -1; + int x = first_pixel; + while (x <= last_pixel) { + if (x == _cursor_pixel && !_scroll_mode) { + draw_cursor(x); + x++; + + } else { + double time = pixel_to_timestamp(x); + frame_number = thread_data->get_frame_number_at_time(time, frame_number); + int w = 1; + int stop_pixel = last_pixel; + if (!_scroll_mode) { + stop_pixel = min(stop_pixel, _cursor_pixel); + } + while (x + w < stop_pixel && + thread_data->get_frame_number_at_time(pixel_to_timestamp(x + w), frame_number) == frame_number) { + w++; + } + if (thread_data->has_frame(frame_number)) { + draw_slice(x, w, get_frame_data(frame_number)); + } else { + draw_empty(x, w); + } + x += w; + } + } + } + + end_draw(first_pixel, last_pixel); +} + +/** + * Erases all elements from the label usage data. + */ +void PStatStripChart:: +clear_label_usage() { + _label_usage.clear(); +} + +/** + * Erases the indicated frame data from the current label usage. This + * indicates that the given FrameData has fallen off the end of the chart. + * This must have been proceeded by an earlier call to inc_label_usage() for + * the same FrameData + */ +void PStatStripChart:: +dec_label_usage(const FrameData &fdata) { + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + nassertv(cd._collector_index < (int)_label_usage.size()); + nassertv(_label_usage[cd._collector_index] > 0); + _label_usage[cd._collector_index]--; + if (_label_usage[cd._collector_index] == 0) { + // If a label drops out of usage, it's time to regenerate labels. + _level_index = -1; + } + } +} + +/** + * Records the labels named in the indicated FrameData in the table of current + * labels in use. This should be called when the given FrameData has been + * added to the chart; it will increment the reference count for each + * collector named in the FrameData. The reference count will eventually be + * decremented when dec_label_usage() is called later. + */ +void PStatStripChart:: +inc_label_usage(const FrameData &fdata) { + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + while (cd._collector_index >= (int)_label_usage.size()) { + _label_usage.push_back(0); + } + nassertv(_label_usage[cd._collector_index] >= 0); + _label_usage[cd._collector_index]++; + if (_label_usage[cd._collector_index] == 1) { + // If a label appears for the first time, it's time to regenerate + // labels. + _level_index = -1; + } + } +} diff --git a/pandatool/src/pstatserver/pStatStripChart.h b/pandatool/src/pstatserver/pStatStripChart.h new file mode 100644 index 00000000..0a68fee6 --- /dev/null +++ b/pandatool/src/pstatserver/pStatStripChart.h @@ -0,0 +1,158 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatStripChart.h + * @author drose + * @date 2000-07-15 + */ + +#ifndef PSTATSTRIPCHART_H +#define PSTATSTRIPCHART_H + +#include "pandatoolbase.h" + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" + +#include "luse.h" +#include "vector_int.h" + +#include "pmap.h" + +class PStatView; + +/** + * This is an abstract class that presents the interface for drawing a basic + * strip-chart, showing the relative value over an interval of time for + * several different collectors, differentiated by bands of color. + * + * This class just manages all the strip-chart logic; the actual nuts and + * bolts of drawing pixels is left to a user-derived class. + */ +class PStatStripChart : public PStatGraph { +public: + PStatStripChart(PStatMonitor *monitor, + int thread_index, int collector_index, bool show_level, + int xsize, int ysize); + virtual ~PStatStripChart(); + + void new_data(int frame_number); + void update(); + bool first_data() const; + + INLINE PStatView &get_view() const; + INLINE int get_thread_index() const; + INLINE int get_collector_index() const; + void set_collector_index(int collector_index); + + INLINE void set_horizontal_scale(double time_width); + INLINE double get_horizontal_scale() const; + INLINE void set_vertical_scale(double value_height); + void set_default_vertical_scale(); + void set_auto_vertical_scale(); + INLINE double get_vertical_scale() const; + + INLINE void set_scroll_mode(bool scroll_mode); + INLINE bool get_scroll_mode() const; + + INLINE void set_average_mode(bool average_mode); + INLINE bool get_average_mode() const; + + int get_collector_under_pixel(int xpoint, int ypoint); + INLINE int timestamp_to_pixel(double time) const; + INLINE double pixel_to_timestamp(int x) const; + INLINE int height_to_pixel(double value) const; + INLINE double pixel_to_height(int y) const; + + INLINE bool is_title_unknown() const; + std::string get_title_text(); + std::string get_total_text(); + std::string get_label_tooltip(int collector_index) const; + + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + +protected: + class ColorData { + public: + unsigned short _collector_index; + unsigned short _i; + int _count; + double _net_value; + }; + class FrameData : public pvector { + public: + double _start, _end; + double _net_value, _net_time; + }; + typedef pmap Data; + + static void accumulate_frame_data(FrameData &fdata, + const FrameData &additional, double weight); + static void scale_frame_data(FrameData &fdata, double factor); + + const FrameData &get_frame_data(int frame_number) const; + void compute_average_pixel_data(PStatStripChart::FrameData &result, + int &then_i, int &now_i, double now); + double get_average_net_value() const; + + void changed_size(int xsize, int ysize); + void force_redraw(); + void force_reset(); + virtual void update_labels(); + virtual void normal_guide_bars(); + + virtual void clear_region(); + virtual void copy_region(int start_x, int end_x, int dest_x); + virtual void begin_draw(int from_x, int to_x); + virtual void draw_slice(int x, int w, const FrameData &fdata); + virtual void draw_empty(int x, int w); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + virtual void idle(); + + INLINE bool is_label_used(int collector_index) const; + +private: + void draw_frames(int first_frame, int last_frame); + void draw_pixels(int first_pixel, int last_pixel); + + void clear_label_usage(); + void dec_label_usage(const FrameData &fdata); + void inc_label_usage(const FrameData &fdata); + +protected: + int _thread_index; + +private: + PStatView &_view; + int _collector_index; + bool _scroll_mode; + bool _average_mode; + + mutable Data _data; + + int _next_frame; + bool _first_data; + int _cursor_pixel; + + int _level_index; + + double _time_width; + double _start_time; + double _value_height; + bool _title_unknown; + + typedef vector_int LabelUsage; + LabelUsage _label_usage; +}; + +#include "pStatStripChart.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatThreadData.I b/pandatool/src/pstatserver/pStatThreadData.I new file mode 100644 index 00000000..aecd834c --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.I @@ -0,0 +1,28 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatThreadData.I + * @author drose + * @date 2000-07-10 + */ + +/** + * Returns a pointer to the ClientData structure associated with this data. + */ +INLINE const PStatClientData *PStatThreadData:: +get_client_data() const { + return _client_data; +} + +/** + * Returns true if the structure contains no frames, false otherwise. + */ +INLINE bool PStatThreadData:: +is_empty() const { + return _frames.empty(); +} diff --git a/pandatool/src/pstatserver/pStatThreadData.cxx b/pandatool/src/pstatserver/pStatThreadData.cxx new file mode 100644 index 00000000..f7bf9e2c --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.cxx @@ -0,0 +1,462 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatThreadData.cxx + * @author drose + * @date 2000-07-09 + */ + +#include "pStatThreadData.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "config_pstatclient.h" + + +PStatFrameData PStatThreadData::_null_frame; + +/** + * + */ +PStatThreadData:: +PStatThreadData(const PStatClientData *client_data) : + _client_data(client_data) +{ + _first_frame_number = 0; + _history = pstats_history; + _computed_elapsed_frames = false; +} + +/** + * + */ +PStatThreadData:: +~PStatThreadData() { +} + +/** + * Returns the frame number of the most recent frame stored in the data. + */ +int PStatThreadData:: +get_latest_frame_number() const { + nassertr(!_frames.empty(), 0); + return _first_frame_number + _frames.size() - 1; +} + +/** + * Returns the frame number of the oldest frame still stored in the data. + */ +int PStatThreadData:: +get_oldest_frame_number() const { + nassertr(!_frames.empty(), 0); + return _first_frame_number; +} + +/** + * Returns true if we have received data for the indicated frame number from + * the client and we still have it stored, or false otherwise. + */ +bool PStatThreadData:: +has_frame(int frame_number) const { + int rel_frame = frame_number - _first_frame_number; + + return (rel_frame >= 0 && rel_frame < (int)_frames.size() && + _frames[rel_frame] != nullptr); +} + +/** + * Returns a FrameData structure associated with the indicated frame number. + * If the frame data has not yet been received from the client, returns the + * newest frame older than the requested frame. + */ +const PStatFrameData &PStatThreadData:: +get_frame(int frame_number) const { + int rel_frame = frame_number - _first_frame_number; + int num_frames = _frames.size(); + if (rel_frame >= num_frames) { + rel_frame = num_frames - 1; + } + + while (rel_frame >= 0 && _frames[rel_frame] == nullptr) { + rel_frame--; + } + if (rel_frame < 0) { + // No frame data that old. Return the oldest frame we've got. + rel_frame = 0; + while (rel_frame < num_frames && + _frames[rel_frame] == nullptr) { + rel_frame++; + } + } + + if (rel_frame >= 0 && rel_frame < num_frames) { + PStatFrameData *frame = _frames[rel_frame]; + nassertr(frame != nullptr, _null_frame); + nassertr(frame->get_start() >= 0.0, _null_frame); + return *frame; + } + + nassertr(_null_frame.get_start() >= 0.0, _null_frame); + return _null_frame; +} + +/** + * Returns the timestamp (in seconds elapsed since connection) of the latest + * available frame. + */ +double PStatThreadData:: +get_latest_time() const { + nassertr(!_frames.empty(), 0.0); + return _frames.back()->get_start(); +} + +/** + * Returns the timestamp (in seconds elapsed since connection) of the oldest + * available frame. + */ +double PStatThreadData:: +get_oldest_time() const { + nassertr(!_frames.empty(), 0.0); + return _frames.front()->get_start(); +} + +/** + * Returns the FrameData structure associated with the latest frame not later + * than the indicated time. + */ +const PStatFrameData &PStatThreadData:: +get_frame_at_time(double time) const { + return get_frame(get_frame_number_at_time(time)); +} + +/** + * Returns the frame number of the latest frame not later than the indicated + * time. If no frames were found, returns get_oldest_frame_number() - 1. + * + * If the hint is nonnegative, it represents a frame number (for which + * has_frame() must return true) we believe the correct answer to be after and + * close by, which may speed the search for the frame. + */ +int PStatThreadData:: +get_frame_number_at_time(double time, int hint) const { + hint -= _first_frame_number; + if (hint >= 0 && hint < (int)_frames.size()) { + if (_frames[hint] != nullptr && + _frames[hint]->get_start() <= time) { + // The hint might be right. Scan forward from there. + int i = hint + 1; + while (i < (int)_frames.size() && + (_frames[i] == nullptr || + _frames[i]->get_start() <= time)) { + if (_frames[i] != nullptr) { + hint = i; + } + ++i; + } + return _first_frame_number + hint; + } + } + + // The hint is wrong, we have to search the entire set. + int first_i = 0; + int last_i = _frames.size() - 1; + while (first_i < last_i && _frames[first_i] == nullptr) { + ++first_i; + } + while (first_i < last_i && _frames[last_i] == nullptr) { + --last_i; + } + + if (first_i >= last_i) { + // There are no frames. + return _first_frame_number - 1; + } + + // Take a guess by interpolating based on the first and last frame times. + double first = _frames[first_i]->get_start(); + if (time < first) { + return _first_frame_number - 1; + } + + double last = _frames[last_i]->get_start(); + double t = (time - first) / (last - first); + hint = std::max(0, (int)(t * (last_i - first_i))) + first_i; + + // Find a frame around the guess that has data. + if (_frames[hint] == nullptr) { + if (hint <= first_i) { + hint = first_i; + } + else do { + // Skip backward until we find a frame with data. + --hint; + } while (_frames[hint] == nullptr); + } + + if (_frames[hint]->get_start() <= time) { + // Search forward. + for (int i = hint + 1; i < (int)_frames.size(); ++i) { + const PStatFrameData *frame = _frames[i]; + if (frame != nullptr) { + if (frame->get_start() <= time) { + hint = i; + } else { + break; + } + } + } + return _first_frame_number + hint; + } + + // Search backward. + for (int i = hint - 1; i >= 0; --i) { + const PStatFrameData *frame = _frames[i]; + if (frame != nullptr && frame->get_start() <= time) { + return _first_frame_number + i; + } + } + + return _first_frame_number - 1; +} + +/** + * Returns the frame number of the first frame later than the indicated time + * and start frame number. + */ +int PStatThreadData:: +get_frame_number_after(double time, int start_at) const { + int i = std::max(0, start_at - _first_frame_number); + double end = get_frame(i).get_end(); + + while (end < time) { + ++i; + if ((size_t)i >= _frames.size()) { + break; + } + if (_frames[i] != nullptr) { + end = _frames[i]->get_end(); + } + } + + return _first_frame_number + i; +} + +/** + * Returns the FrameData associated with the most recent frame. + */ +const PStatFrameData &PStatThreadData:: +get_latest_frame() const { + nassertr(!_frames.empty(), _null_frame); + return *_frames.back(); +} + +/** + * Computes the oldest frame number not older than pstats_average_time + * seconds, and the newest frame number. Handy for computing average frame + * rate over a time. Returns true if there is any data in that range, false + * otherwise. + */ +bool PStatThreadData:: +get_elapsed_frames(int &then_i, int &now_i) const { + if (!_computed_elapsed_frames) { + compute_elapsed_frames(); + } + + now_i = _now_i; + then_i = _then_i; + return _got_elapsed_frames; +} + +/** + * Computes the average frame rate over the past pstats_average_time seconds, + * by counting up the number of frames elapsed in that time interval. + */ +double PStatThreadData:: +get_frame_rate() const { + int then_i, now_i; + if (!get_elapsed_frames(then_i, now_i)) { + return 0.0f; + } + + int num_frames = now_i - then_i + 1; + double now = _frames[now_i - _first_frame_number]->get_end(); + double elapsed_time = (now - _frames[then_i - _first_frame_number]->get_start()); + return (double)num_frames / elapsed_time; +} + + +/** + * Sets the number of seconds worth of frames that will be retained by the + * ThreadData structure as each new frame is added. This affects how old the + * oldest frame that may be queried is. + */ +void PStatThreadData:: +set_history(double time) { + _history = time; +} + +/** + * Returns the number of seconds worth of frames that will be retained by the + * ThreadData structure as each new frame is added. This affects how old the + * oldest frame that may be queried is. + */ +double PStatThreadData:: +get_history() const { + return _history; +} + +/** + * Given a timestamp representing the time of the latest known frame, removes + * any frames older than the configured history. Returns true if the data is + * now empty. + */ +bool PStatThreadData:: +prune_history(double time) { + double oldest_allowable_time = time - _history; + while (!_frames.empty() && + (_frames.front() == nullptr || + _frames.front()->is_time_empty() || + _frames.front()->get_start() < oldest_allowable_time)) { + delete _frames.front(); + _frames.pop_front(); + _first_frame_number++; + } + + return _frames.empty(); +} + +/** + * Makes room for and stores a new frame's worth of data. Calling this + * function may cause old frame data to be discarded to make room, according + * to the amount of time set up via set_history(). + * + * The pointer will become owned by the PStatThreadData object and will be + * freed on destruction. + */ +void PStatThreadData:: +record_new_frame(int frame_number, PStatFrameData *frame_data) { + nassertv(frame_data != nullptr); + nassertv(!frame_data->is_empty()); + + // First, remove all the old frames that fall outside of our history window. + // Then, add enough empty frame definitions to account for the latest frame + // number. This might involve some skips, since we don't guarantee that we + // get all the frames in order or even at all. + if (prune_history(frame_data->get_start())) { + _first_frame_number = frame_number; + _frames.push_back(nullptr); + } + else { + while (_first_frame_number + (int)_frames.size() <= frame_number) { + _frames.push_back(nullptr); + } + } + + int index = frame_number - _first_frame_number; + + // It's possible to receive frames out of order. + while (index < 0) { + _frames.push_front(nullptr); + ++index; + --_first_frame_number; + } + + nassertv(index >= 0 && index < (int)_frames.size()); + + if (_frames[index] != nullptr) { + nout << "Got repeated frame data for frame " << frame_number << "\n"; + delete _frames[index]; + } + + _frames[index] = frame_data; + _computed_elapsed_frames = false; +} + +/** + * Writes the thread data to a datagram. + */ +void PStatThreadData:: +write_datagram(Datagram &dg) const { + int frame_number = _first_frame_number; + + for (PStatFrameData *frame_data : _frames) { + if (frame_data != nullptr) { + dg.add_int32(frame_number); + frame_data->write_datagram(dg); + } + ++frame_number; + } + dg.add_int32(-1); +} + +/** + * Restores the thread data from a datagram. + */ +void PStatThreadData:: +read_datagram(DatagramIterator &scan, PStatClientVersion *version) { + int frame_number; + while ((frame_number = scan.get_int32()) != -1) { + PStatFrameData *frame_data = new PStatFrameData; + frame_data->read_datagram(scan, version); + + record_new_frame(frame_number, frame_data); + } + + compute_elapsed_frames(); +} + +/** + * Computes the frame numbers returned by get_elapsed_frames(). + */ +void PStatThreadData:: +compute_elapsed_frames() const { + if (_frames.empty()) { + // No frames in the data at all. + _got_elapsed_frames = false; + } + else { + _now_i = _frames.size() - 1; + while (_now_i > 0 && _frames[_now_i] == nullptr) { + _now_i--; + } + if (_now_i < 0) { + // No frames have any real data. + _got_elapsed_frames = false; + } + else { + nassertv(_frames[_now_i] != nullptr); + + double now = _frames[_now_i]->get_end(); + double then = now - pstats_average_time; + + int old_i = _now_i; + _then_i = _now_i; + + while (old_i >= 0) { + const PStatFrameData *frame = _frames[old_i]; + if (frame != nullptr) { + if (frame->get_start() > then) { + _then_i = old_i; + } else { + break; + } + } + old_i--; + } + + nassertv(_then_i >= 0); + nassertv(_frames[_then_i] != nullptr); + _got_elapsed_frames = true; + + _now_i += _first_frame_number; + _then_i += _first_frame_number; + } + } + + _computed_elapsed_frames = true; +} diff --git a/pandatool/src/pstatserver/pStatThreadData.h b/pandatool/src/pstatserver/pStatThreadData.h new file mode 100644 index 00000000..471312d2 --- /dev/null +++ b/pandatool/src/pstatserver/pStatThreadData.h @@ -0,0 +1,92 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatThreadData.h + * @author drose + * @date 2000-07-08 + */ + +#ifndef PSTATTHREADDATA_H +#define PSTATTHREADDATA_H + +#include "pandatoolbase.h" +#include "datagram.h" +#include "datagramIterator.h" +#include "referenceCount.h" + +#include "pdeque.h" + +class PStatCollectorDef; +class PStatFrameData; +class PStatClientData; +class PStatClientVersion; + +/** + * A collection of FrameData structures for recently-received frames within a + * particular thread. This holds the raw data as reported by the client, and + * it automatically handles frames received out-of-order or skipped. You can + * ask for a particular frame by frame number or time and receive the data for + * the nearest frame. + */ +class PStatThreadData : public ReferenceCount { +public: + PStatThreadData(const PStatClientData *client_data); + ~PStatThreadData(); + + INLINE const PStatClientData *get_client_data() const; + + INLINE bool is_empty() const; + + int get_latest_frame_number() const; + int get_oldest_frame_number() const; + bool has_frame(int frame_number) const; + const PStatFrameData &get_frame(int frame_number) const; + + double get_latest_time() const; + double get_oldest_time() const; + const PStatFrameData &get_frame_at_time(double time) const; + int get_frame_number_at_time(double time, int hint = -1) const; + int get_frame_number_after(double time, int start_at = 0) const; + + const PStatFrameData &get_latest_frame() const; + + bool get_elapsed_frames(int &then_i, int &now_i) const; + double get_frame_rate() const; + + + void set_history(double time); + double get_history() const; + bool prune_history(double time); + + void record_new_frame(int frame_number, PStatFrameData *frame_data); + + void write_datagram(Datagram &dg) const; + void read_datagram(DatagramIterator &scan, PStatClientVersion *version); + +private: + void compute_elapsed_frames() const; + + const PStatClientData *_client_data; + + typedef pdeque Frames; + Frames _frames; + int _first_frame_number; + double _history; + + // Cached values, updated by compute_elapsed_frames(). + mutable bool _computed_elapsed_frames; + mutable bool _got_elapsed_frames; + mutable int _then_i; + mutable int _now_i; + + static PStatFrameData _null_frame; +}; + +#include "pStatThreadData.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatTimeline.I b/pandatool/src/pstatserver/pStatTimeline.I new file mode 100644 index 00000000..e3daa44a --- /dev/null +++ b/pandatool/src/pstatserver/pStatTimeline.I @@ -0,0 +1,151 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatTimeline.I + * @author rdb + * @date 2022-02-11 + */ + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +INLINE void PStatTimeline:: +set_horizontal_scale(double time_width) { + double max_time_width = (_highest_end_time - _lowest_start_time) * 2.0; + time_width = (std::min)(time_width, max_time_width); + + double scale = time_width / get_xsize(); + if (_time_scale != scale) { + _time_scale = scale; + _target_time_scale = scale; + _zoom_speed = 0.0; + normal_guide_bars(); + force_redraw(); + } +} + +/** + * Returns the amount of total time the width of the horizontal axis + * represents. + */ +INLINE double PStatTimeline:: +get_horizontal_scale() const { + return _time_scale * get_xsize(); +} + +/** + * This may force a redraw. + */ +INLINE void PStatTimeline:: +set_horizontal_scroll(double start_time) { + start_time = (std::max)((std::min)(start_time, _highest_end_time), _lowest_start_time); + if (_start_time != start_time) { + _start_time = start_time; + _target_start_time = start_time; + _scroll_speed = 0.0; + normal_guide_bars(); + force_redraw(); + } +} + +/** + * Returns the amount of total time the width of the horizontal axis + * represents. + */ +INLINE double PStatTimeline:: +get_horizontal_scroll() const { + return _start_time; +} + +/** + * Smoothly zooms to the given time width, around the given focal point. + */ +INLINE void PStatTimeline:: +zoom_to(double time_width, double center) { + // Don't allow zooming out to beyond 2x the size of the entire timeline. + // There's a limit of zooming beyond 1 ns per bar, there's just no point... + double max_time_width = (_highest_end_time - _lowest_start_time) * 2.0; + time_width = (std::min)((std::max)(1e-7, time_width), max_time_width); + _target_time_scale = time_width / get_xsize(); + _zoom_center = center; + + double pivot_x = (_zoom_center - _start_time) / _time_scale; + scroll_to(_zoom_center - pivot_x * _target_time_scale); +} + +/** + * Smoothly zooms by the given amount, where 1.0 is a single "tick" of zooming + * in and -1.0 is a single "tick" of zooming out. + */ +INLINE void PStatTimeline:: +zoom_by(double amount, double center) { + zoom_to(_target_time_scale * pow(0.8, amount) * get_xsize(), center); +} + +/** + * Smoothly scrolls to the given time point. + */ +INLINE void PStatTimeline:: +scroll_to(double start_time) { + _target_start_time = (std::max)((std::min)(start_time, _highest_end_time), _lowest_start_time); +} + +/** + * Smoothly scrolls by the given amount. + */ +INLINE void PStatTimeline:: +scroll_by(double delta) { + scroll_to(_target_start_time + delta); +} + +/** + * Converts a timestamp to a horizontal pixel offset. + */ +INLINE int PStatTimeline:: +timestamp_to_pixel(double time) const { + return (int)((double)_xsize * (time - _start_time) / get_horizontal_scale()); +} + +/** + * Converts a horizontal pixel offset to a timestamp. + */ +INLINE double PStatTimeline:: +pixel_to_timestamp(int x) const { + return _time_scale * (double)x + _start_time; +} + +/** + * Converts a value (i.e. a "height" in the strip chart) to a horizontal + * pixel offset. + */ +INLINE int PStatTimeline:: +height_to_pixel(double value) const { + return (int)((double)_xsize * value / get_horizontal_scale()); +} + +/** + * Converts a horizontal pixel offset to a value (a "height" in the strip + * chart). + */ +INLINE double PStatTimeline:: +pixel_to_height(int x) const { + return _time_scale * (double)x; +} + +/** + * Returns the total number of rows. + */ +INLINE int PStatTimeline:: +get_num_rows() const { + if (_threads.empty()) { + return 0; + } else { + return _threads.back()._row_offset + _threads.back()._rows.size(); + } +} diff --git a/pandatool/src/pstatserver/pStatTimeline.cxx b/pandatool/src/pstatserver/pStatTimeline.cxx new file mode 100644 index 00000000..d8d0fdfb --- /dev/null +++ b/pandatool/src/pstatserver/pStatTimeline.cxx @@ -0,0 +1,915 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatTimeline.cxx + * @author rdb + * @date 2022-02-11 + */ + +#include "pStatTimeline.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" +#include "string_utils.h" +#include "config_pstatclient.h" + +#include + +/** + * + */ +PStatTimeline:: +PStatTimeline(PStatMonitor *monitor, int xsize, int ysize) : + PStatGraph(monitor, xsize, ysize) +{ + // Default to 1 millisecond per 10 pixels. + _time_scale = 1 / 10000.0; + _target_time_scale = _time_scale; + + _guide_bar_units = GBU_ms | GBU_show_units; + + // Load in the initial data, so that the user can see everything back to the + // beginning (or as far as pstats-history goes back to). + const PStatClientData *client_data = monitor->get_client_data(); + if (client_data != nullptr) { + size_t row_offset = 0; + + // Ignore this nasty "inverted" collector, which messes up everything. + _app_collector_index = client_data->find_collector("App:Show code:General"); + + for (int thread_index = 0; thread_index < client_data->get_num_threads(); ++thread_index) { + _threads.emplace_back(); + ThreadRow &thread_row = _threads.back(); + thread_row._row_offset = row_offset; + + if (!client_data->has_thread(thread_index)) { + continue; + } + thread_row._visible = true; + + const PStatThreadData *thread_data = client_data->get_thread_data(thread_index); + if (thread_data != nullptr) { + _threads_changed = true; + + if (!thread_data->is_empty()) { + int oldest_frame = thread_data->get_oldest_frame_number(); + int latest_frame = thread_data->get_latest_frame_number(); + + double oldest_start_time = thread_data->get_frame(oldest_frame).get_start(); + double latest_end_time = thread_data->get_frame(latest_frame).get_end(); + + if (!_have_start_time) { + _have_start_time = true; + _lowest_start_time = oldest_start_time; + } + else { + _lowest_start_time = std::min(_lowest_start_time, oldest_start_time); + } + _highest_end_time = std::max(_highest_end_time, latest_end_time + _clock_skew); + + for (int frame = oldest_frame; frame <= latest_frame; ++frame) { + if (thread_data->has_frame(frame)) { + update_bars(thread_index, frame); + } + } + } + } + + row_offset += thread_row._rows.size() + 1; + } + } + + _start_time = _lowest_start_time; + _target_start_time = _start_time; + + monitor->_timelines.insert(this); +} + +/** + * + */ +PStatTimeline:: +~PStatTimeline() { + _monitor->_timelines.erase(this); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void PStatTimeline:: +new_data(int thread_index, int frame_number) { + const PStatClientData *client_data = _monitor->get_client_data(); + + if (client_data != nullptr) { + const PStatThreadData *thread_data = + client_data->get_thread_data(thread_index); + + if (thread_data != nullptr && thread_data->has_frame(frame_number)) { + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + double frame_start = frame_data.get_start() + _clock_skew; + double frame_end = frame_data.get_end() + _clock_skew; + + if (thread_index == 0 && _app_collector_index == -1) { + _app_collector_index = client_data->find_collector("App:Show code:General"); + } + + if (!_have_start_time) { + _start_time = frame_start; + _have_start_time = true; + _lowest_start_time = _start_time; + } + else if (_start_time < _lowest_start_time) { + _lowest_start_time = _start_time; + } + if (frame_end > _highest_end_time) { + _highest_end_time = frame_end; + } + + while (thread_index >= (int)_threads.size()) { + _threads_changed = true; + if (_threads.size() == 0) { + _threads.resize(1); + } else { + _threads.resize(_threads.size() + 1); + _threads[_threads.size() - 1]._row_offset = + _threads[_threads.size() - 2]._row_offset; + if (_threads[_threads.size() - 2]._visible) { + _threads[_threads.size() - 1]._row_offset += + _threads[_threads.size() - 2]._rows.size() + 1; + } + } + } + + if (update_bars(thread_index, frame_number)) { + // The number of rows was changed. + // Change the offset of all subsequent ThreadRows. + ThreadRow &thread_row = _threads[thread_index]; + size_t offset = thread_row._row_offset + thread_row._rows.size() + 1; + for (size_t ti = (size_t)(thread_index + 1); ti < _threads.size(); ++ti) { + _threads[ti]._row_offset = offset; + if (_threads[ti]._visible) { + offset += _threads[ti]._rows.size() + 1; + } + } + _threads_changed = true; + normal_guide_bars(); + force_redraw(); + } + else if (frame_end >= _start_time || frame_start <= _start_time + get_horizontal_scale()) { + normal_guide_bars(); + begin_draw(); + draw_thread(thread_index, frame_start, frame_end); + end_draw(); + } + } + } + + idle(); +} + +/** + * Called by new_data(). Updates the bars without doing any drawing. Returns + * true if the number of rows was changed (forcing a full redraw), false if + * only new bars were added on the right side. + */ +bool PStatTimeline:: +update_bars(int thread_index, int frame_number) { + const PStatClientData *client_data = _monitor->get_client_data(); + const PStatThreadData *thread_data = client_data->get_thread_data(thread_index); + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + ThreadRow &thread_row = _threads[thread_index]; + thread_row._label = client_data->get_thread_name(thread_index); + bool changed_num_rows = false; + + if (!thread_row._visible) { + thread_row._visible = true; + changed_num_rows = true; + } + + // pair + pvector > stack; + bool had_children = false; + + double frame_start = frame_data.get_start() + _clock_skew; + double prev = frame_start; + + // There may still be open collectors from the previous frame. Rebuild the + // stack based on that so we can close them properly. + for (size_t i = 0; i < thread_row._rows.size(); ++i) { + Row &row = thread_row._rows[i]; + + // Keep iterating from the back until we get to the previous frame. + for (size_t j = 0; j < row.size(); ++j) { + ColorBar &bar = row[row.size() - j - 1]; + if (bar._frame_number < frame_number) { + // Was this collector left unstopped at the end of the last frame? + if (bar._open_end && bar._frame_number == frame_number - 1) { + if (i >= stack.size()) { + stack.resize(i + 1, std::make_pair(-1, 0.0)); + } + stack[i] = std::make_pair(bar._collector_index, bar._start); + + // Remove this bar for now, we'll recreate it when we close it. + row.erase(row.begin() + (row.size() - j - 1)); + } + break; + } + } + } + + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { + int collector_index = frame_data.get_time_collector(i); + double time = frame_data.get_time(i) + _clock_skew; + + if (time < prev) { + // Apparently, it is too hard to ask for monotonically increasing time + // values! Don't be tempted to sort the values, or think this is a bug, + // the client can arbitrarily move back the clock if it feels like it! + // I had some fancy mechanism that handled clock skew properly for + // multiple out-of-order frames, but I gave up when I realised it's not + // possible to solve this problem for multiple threads, so we just assume + // this is done just once at startup before the other threads exist. + double delta = prev - time; + _clock_skew += delta; + _highest_end_time += delta; + time = prev; + nout << "Correcting for clock skew of " + << format_number(delta, GBU_show_units | GBU_ms) + << " in frame " << frame_number << " of thread " + << thread_index << "\n"; + + // Move all bars after this frame to the right by this amount. + for (ThreadRow &thread_row : _threads) { + for (Row &row : thread_row._rows) { + for (ColorBar &bar : row) { + if (bar._frame_number > frame_number) { + bar._start += delta; + bar._end += delta; + } + } + } + } + } + prev = time; + + if (collector_index == _app_collector_index) { + continue; + } + + if (frame_data.is_start(i)) { + if (collector_index == 0 && !stack.empty()) { + // Collector 0 always goes at the top, no matter what. + stack[0].first = 0; + stack[0].second = std::max(time, _start_time); + } else { + stack.push_back(std::make_pair(collector_index, std::max(time, _start_time))); + if (stack.size() > thread_row._rows.size()) { + thread_row._rows.resize(stack.size()); + changed_num_rows = true; + } + had_children = false; + } + } + else if (!stack.empty() && stack.back().first == collector_index) { + // Most likely case, ending the most recent collector that is still open. + double start_time = stack.back().second; + stack.pop_back(); + thread_row._rows[stack.size()].push_back({ + start_time, time, collector_index, thread_index, frame_number, false, false}); + + // Pop off stack levels for prematurely ended collectors (see below). + while (!stack.empty() && stack.back().first < 0) { + stack.pop_back(); + } + had_children = true; + } + else { + // Unlikely case: ending a collector before a "child" has ended. + // Go back and clear the row where this collector started. + // Don't decrement the row index. + size_t i; + for (i = 0; i < stack.size(); ++i) { + auto &item = stack[stack.size() - 1 - i]; + + if (item.first == collector_index) { + thread_row._rows[stack.size() - 1 - i].push_back({ + item.second, time, collector_index, thread_index, frame_number, false, false}); + item.first = -1; + break; + } + } + if (i == stack.size()) { + // We stopped a collector that wasn't started at all. That means it + // must have already been running when this frame started, which we + // normally handle by pre-populating the stack with the open collectors + // from last frame, but maybe we didn't get the last frame (yet). + // Unfortunately we need to add it above any bars we've already started + // and stopped, which means we need to shift everything down belonging + // to this frame, except for the top-level Frame collector. This is + // probably pretty inefficient, but it should be pretty rare for frames + // to arrive out-of-order in this manner. + nassertd(!stack.empty()) continue; + stack.insert(stack.begin() + 1, std::make_pair(-1, 0.0)); + + const size_t num_rows = thread_row._rows.size(); + for (size_t j = num_rows - 1; j >= 1; --j) { + size_t row_size = thread_row._rows[j].size(); + for (size_t k = row_size; k > 0; --k) { + Row &row = thread_row._rows[j]; + ColorBar bar = row[k - 1]; + if (bar._frame_number == frame_number) { + row.erase(row.begin() + (k - 1)); + if (j + 1 >= thread_row._rows.size()) { + thread_row._rows.resize(j + 2); + changed_num_rows = true; + } + + // Insert it into the row below while retaining sorting. + Row &row2 = thread_row._rows[j + 1]; + row2.insert(std::upper_bound(row2.begin(), row2.end(), bar), bar); + } + else if (bar._frame_number < frame_number) { + break; + } + } + } + + // Now insert the bar, just below Frame level. + if (thread_row._rows.size() < 2) { + thread_row._rows.resize(2); + changed_num_rows = true; + } + thread_row._rows[1].push_back({ + frame_start, time, collector_index, thread_index, + frame_number, true, false}); + } + else if (i > 0 && !had_children) { + // Figure out if the currently active collector could actually be + // slotted higher. This prevents the staircase effect where + // overlapping collectors will cause the number of rows to continue + // to grow at every overlap. We only do this if the current + // collector has not had children yet, that'd be too confusing. + int j = stack.size() - 3; + while (j > 0) { + auto &item = stack[j]; + if (item.first >= 0) { + // Nope, can't do. + break; + } + // Yes, does this row have enough space? + Row &row = thread_row._rows[j]; + if (row.empty() || row.back()._end < stack.back().second) { + // It does. + item = std::move(stack.back()); + stack.pop_back(); + while (!stack.empty() && stack.back().first < 0) { + stack.pop_back(); + } + break; + } + --j; + } + } + } + } + + // Add all unclosed bars, marking them as open-ended, for later gluing. + while (!stack.empty()) { + int collector_index = stack.back().first; + if (collector_index >= 0) { + double start_time = stack.back().second; + thread_row._rows[stack.size() - 1].push_back({ + start_time, frame_data.get_end() + _clock_skew, + collector_index, thread_index, frame_number, false, true, + }); + } + stack.pop_back(); + } + + if (thread_row._last_frame >= 0 && frame_number < thread_row._last_frame) { + // Added a frame out of order. + for (Row &row : thread_row._rows) { + // Sort by end time. + std::sort(row.begin(), row.end()); + + // Glue together open ends and beginnings that match up. + size_t end = row.size() - 1; + for (size_t i = 0; i < end;) { + ColorBar &left = row[i]; + ColorBar &right = row[i + 1]; + if (left._collector_index == right._collector_index && + left._frame_number + 1 == right._frame_number && + left._open_end && right._open_begin) { + // Erase the left one, to maintain the sorting by end time. + right._open_begin = false; + right._start = left._start; + row.erase(row.begin() + i); + --end; + } else { + ++i; + } + } + } + } else { + thread_row._last_frame = frame_number; + } + + return changed_num_rows; +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string PStatTimeline:: +get_bar_tooltip(int row, int x) const { + ColorBar bar; + if (find_bar(row, x, bar)) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->has_collector(bar._collector_index)) { + std::ostringstream text; + text << client_data->get_collector_fullname(bar._collector_index); + text << " ("; + if (bar._open_begin || bar._open_end) { + text << "at least "; + } + text << format_number(bar._end - bar._start, GBU_show_units | GBU_ms) << ")"; + return text.str(); + } + } + return std::string(); +} + +/** + * Writes the graph state to a datagram. + */ +void PStatTimeline:: +write_datagram(Datagram &dg) const { + dg.add_float64(_time_scale); + dg.add_float64(_start_time); + dg.add_float64(_lowest_start_time); + dg.add_float64(_highest_end_time); + + PStatGraph::write_datagram(dg); +} + +/** + * Restores the graph state from a datagram. + */ +void PStatTimeline:: +read_datagram(DatagramIterator &scan) { + _time_scale = scan.get_float64(); + _start_time = scan.get_float64(); + _lowest_start_time = scan.get_float64(); + _highest_end_time = scan.get_float64(); + + _scroll_speed = 0.0; + _zoom_speed = 0.0; + + _have_start_time = true; + _target_start_time = _start_time; + _target_time_scale = _time_scale; + + PStatGraph::read_datagram(scan); + + normal_guide_bars(); + force_redraw(); +} + +/** + * To be called by the user class when the widget size has changed. This + * updates the chart's internal data and causes it to issue redraw commands to + * reflect the new size. + */ +void PStatTimeline:: +changed_size(int xsize, int ysize) { + if (xsize != _xsize || ysize != _ysize) { + _xsize = xsize; + _ysize = ysize; + + normal_guide_bars(); + force_redraw(); + } +} + +/** + * To be called by the user class when the whole thing needs to be redrawn for + * some reason. + */ +void PStatTimeline:: +force_redraw() { + clear_region(); + + begin_draw(); + + for (const GuideBar &bar : _guide_bars) { + int x = timestamp_to_pixel(bar._height); + if (x > 0 && x < get_xsize() - 1) { + draw_guide_bar(x, bar._style); + } + } + + double start_time = _start_time; + double end_time = start_time + get_horizontal_scale(); + + int num_rows = 0; + + for (size_t ti = 0; ti < _threads.size(); ++ti) { + ThreadRow &thread_row = _threads[ti]; + if (thread_row._visible) { + for (size_t ri = 0; ri < thread_row._rows.size(); ++ri) { + draw_row((int)ti, (int)ri, start_time, end_time); + ++num_rows; + } + draw_separator(num_rows++); + } + } + + end_draw(); +} + +/** + * To be called by the user class when the whole thing needs to be redrawn for + * some reason. + */ +void PStatTimeline:: +force_redraw(int row, int from_x, int to_x) { + double start_time = std::max(_start_time, pixel_to_timestamp(from_x)); + double end_time = std::min(_start_time + get_horizontal_scale(), pixel_to_timestamp(to_x)); + + begin_draw(); + + for (size_t ti = 0; ti < _threads.size(); ++ti) { + ThreadRow &thread_row = _threads[ti]; + if (!thread_row._visible || (int)thread_row._row_offset > row) { + break; + } + + int row_index = row - (int)thread_row._row_offset; + if (row_index < (int)thread_row._rows.size()) { + draw_row((int)ti, row_index, start_time, end_time); + } + } + + end_draw(); +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void PStatTimeline:: +normal_guide_bars() { + double start_time = get_horizontal_scroll(); + double time_width = get_horizontal_scale(); + double end_time = start_time + time_width; + + // We want vaguely 150 pixels between guide bars. + int max_frames = get_xsize() / 100; + int l = (int)std::floor(3.0 * log10(pixel_to_height(150)) + 0.5); + double interval = pow(10.0, std::ceil(l / 3.0)); + if ((l + 3000) % 3 == 1) { + interval /= 5; + } + else if ((l + 3000) % 3 == 2) { + interval /= 2; + } + + _guide_bars.clear(); + + // Rather than getting the client data, we look in the color bar data for + // the first row, because the client data gets wiped after a while. + if (!_threads.empty() && !_threads[0]._rows.empty()) { + const Row &row = _threads[0]._rows[0]; + + // Look for the last Frame bar with end time lower than our start time. + Row::const_iterator it = std::lower_bound(row.begin(), row.end(), ColorBar {0.0, start_time}); + while (it != row.end() && it->_collector_index != 0) { + ++it; + } + + int num_frames = 0; + + while (it != row.end() && it->_start <= end_time) { + double frame_start = it->_start; + double frame_end = it->_end; + int frame_number = it->_frame_number; + + if (frame_start > start_time) { + if (!_guide_bars.empty() && height_to_pixel(frame_start - _guide_bars.back()._height) < 30) { + // Get rid of last label, it is in the way. + _guide_bars.back()._label.clear(); + } + std::string label = "#"; + label += format_string(frame_number); + _guide_bars.push_back(GuideBar(frame_start, label, GBS_frame)); + + if (++num_frames > max_frames) { + // Forget it, this is becoming too many lines. + _guide_bars.clear(); + break; + } + } + + do { + ++it; + } + while (it != row.end() && it->_collector_index != 0); + + double frame_width; + if (it != row.end()) { + // Only go up to the start of the next frame, limiting to however much + // fits in the graph. + frame_width = std::min(frame_end - frame_start, end_time - frame_start); + } else { + // Reached the end; just continue to the end of the graph. + frame_width = end_time - frame_start; + } + + if (interval > 0.0) { + int first_bar = std::max((int)((start_time - frame_start) / interval), 1); + int num_bars = (int)std::round(frame_width / interval); + + for (int i = first_bar; i < num_bars; ++i) { + double offset = i * interval; + std::string label = "+"; + label += format_number(offset, GBU_show_units | GBU_ms); + _guide_bars.push_back(GuideBar(frame_start + offset, label, GBS_normal)); + } + } + + // If there's a gap between frames, add another line. + if (it->_start > frame_end && it->_frame_number > frame_number + 1) { + std::string label; + if (it->_start - frame_end >= interval) { + label = "#" + format_string(frame_number + 1); + if (it->_frame_number > frame_number + 2) { + label += "-" + format_string(it->_frame_number - 1); + } + label += " (dropped)"; + } + _guide_bars.push_back(GuideBar(frame_end, label, GBS_frame)); + } + } + } + + if (_guide_bars.empty() && interval > 0.0) { + int first_bar = std::max((int)(start_time / interval), 1); + int num_bars = (int)std::round(end_time / interval); + + for (int i = first_bar; i < num_bars; ++i) { + double time = i * interval; + std::string label = format_number(time, GBU_show_units | GBU_ms); + _guide_bars.push_back(GuideBar(time, label, GBS_frame)); + } + } + + _guide_bars_changed = true; +} + +/** + * Should be overridden by the user class to wipe out the entire strip chart + * region. + */ +void PStatTimeline:: +clear_region() { +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any bars in the chart. + */ +void PStatTimeline:: +begin_draw() { +} + +/** + * + */ +void PStatTimeline:: +draw_thread(int thread_index, double start_time, double end_time) { + if (thread_index < 0 || (size_t)thread_index > _threads.size()) { + return; + } + + ThreadRow &thread_row = _threads[(size_t)thread_index]; + if (thread_row._visible) { + for (size_t ri = 0; ri < thread_row._rows.size(); ++ri) { + draw_row(thread_index, (int)ri, start_time, end_time); + } + } +} + +/** + * + */ +void PStatTimeline:: +draw_row(int thread_index, int row_index, double start_time, double end_time) { + ThreadRow &thread_row = _threads[thread_index]; + Row &row = thread_row._rows[row_index]; + + const PStatClientData *client_data = _monitor->get_client_data(); + + // Find the first element whose end time is larger than our start time. + // Then iterate until at least the end of the frame. + Row::iterator it = std::lower_bound(row.begin(), row.end(), ColorBar {0.0, start_time}); + if (it == row.end()) { + return; + } + + int frame_number = it->_frame_number; + do { + ColorBar &bar = *it; + + int from_x = timestamp_to_pixel(bar._start); + int to_x = timestamp_to_pixel(bar._end); + + if (to_x >= 0 && to_x > from_x && from_x < get_xsize()) { + if (bar._collector_index != 0) { + const PStatCollectorDef &def = client_data->get_collector_def(bar._collector_index); + if (to_x - from_x >= 32 && def._parent_index > 0) { + // Try including the parent name. + const PStatCollectorDef &parent_def = client_data->get_collector_def(def._parent_index); + std::string long_name = parent_def._name + ":" + def._name; + draw_bar(thread_row._row_offset + row_index, from_x, to_x, + bar._collector_index, long_name); + } else { + draw_bar(thread_row._row_offset + row_index, from_x, to_x, + bar._collector_index, def._name); + } + } else { + draw_bar(thread_row._row_offset + row_index, from_x, to_x, + bar._collector_index, + std::string("Frame ") + format_string(bar._frame_number)); + } + } + + ++it; + } + while (it != row.end() && (it->_start <= end_time || it->_frame_number == frame_number)); +} + +/** + * Draws a horizontal separator. + */ +void PStatTimeline:: +draw_separator(int) { +} + +/** + * Draws a vertical guide bar. If the row is -1, draws it in all rows. + */ +void PStatTimeline:: +draw_guide_bar(int x, GuideBarStyle style) { +} + +/** + * Draws a single bar in the chart for the indicated row, in the color for the + * given collector, for the indicated horizontal pixel range. + */ +void PStatTimeline:: +draw_bar(int, int, int, int, const std::string &) { +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the chart. + */ +void PStatTimeline:: +end_draw() { +} + +/** + * Should be overridden by the user class to perform any other updates might + * be necessary after the bars have been redrawn. + */ +void PStatTimeline:: +idle() { +} + +/** + * Should be called periodically to update any animated values. Returns false + * to indicate that the animation is done and no longer needs to be called. + */ +bool PStatTimeline:: +animate(double time, double dt) { + int hmove = ((_keys_held & (F_right | F_d)) != 0) + - ((_keys_held & (F_left | F_a)) != 0); + int vmove = ((_keys_held & F_w) != 0) + - ((_keys_held & F_s) != 0); + + if (hmove > 0) { + if (_scroll_speed < 0) { + _scroll_speed = 1.0; + } + _scroll_speed += 1.0; + } + else if (hmove < 0) { + if (_scroll_speed > 0) { + _scroll_speed = -1.0; + } + _scroll_speed -= 1.0; + } + else if (_scroll_speed != 0.0) { + _scroll_speed *= std::exp(-12.0 * dt); + if (std::abs(_scroll_speed) < 0.2) { + _scroll_speed = 0.0; + } + } + + if (vmove > 0) { + if (_zoom_speed < 0) { + _zoom_speed = 1.0; + } + _zoom_speed += 1.0; + } + else if (vmove < 0) { + if (_zoom_speed > 0) { + _zoom_speed = -1.0; + } + _zoom_speed -= 1.0; + } + else if (_zoom_speed != 0.0) { + _zoom_speed *= std::exp(-12.0 * dt); + if (std::abs(_zoom_speed) < 0.2) { + _zoom_speed = 0.0; + } + } + + if (_zoom_speed != 0.0) { + zoom_to(get_horizontal_scale() * pow(0.5, _zoom_speed * dt), _zoom_center); + } + + if (_scroll_speed != 0.0) { + scroll_by(_scroll_speed * 300 * _time_scale * dt); + } + + if (_target_start_time != _start_time) { + double dist = _target_start_time - _start_time; + // When the difference is less than 2 pixels, snap to target position. + if (std::abs(dist) < _time_scale * 2) { + _start_time = _target_start_time; + } else { + dist *= 1.0 - std::exp(-12.0 * dt); + _start_time += dist; + } + } + + if (_target_time_scale != _time_scale) { + //double dist = std::log(_target_time_scale) - std::log(_time_scale); + double dist = _target_time_scale - _time_scale; + if (_target_start_time == _start_time && std::abs(dist) < 0.01) { + _time_scale = _target_time_scale; + } else { + dist *= 1.0 - std::exp(-12.0 * dt); + //_time_scale *= std::exp(dist); + _time_scale += dist; + } + } + + normal_guide_bars(); + force_redraw(); + + // Stop the animation when the speed is 0 and no key is still held. + return _keys_held != 0 + || _scroll_speed != 0 + || _zoom_speed != 0 + || _target_start_time != _start_time + || _target_time_scale != _time_scale; +} + +/** + * Return the ColorBar at the indicated position. + */ +bool PStatTimeline:: +find_bar(int row, int x, ColorBar &bar) const { + double time = pixel_to_timestamp(x); + + for (size_t ti = 0; ti < _threads.size(); ++ti) { + const ThreadRow &thread_row = _threads[ti]; + if ((int)thread_row._row_offset > row) { + break; + } + + int row_index = row - (int)thread_row._row_offset; + if (row_index < (int)thread_row._rows.size()) { + // Find the first element whose end time is larger than the given time. + const Row &bars = thread_row._rows[row_index]; + Row::const_iterator it = std::lower_bound(bars.begin(), bars.end(), ColorBar {time, time}); + if (it != bars.end() && it->_start <= time) { + bar = *it; + return true; + } + } + } + + return false; +} diff --git a/pandatool/src/pstatserver/pStatTimeline.h b/pandatool/src/pstatserver/pStatTimeline.h new file mode 100644 index 00000000..ebf5a30d --- /dev/null +++ b/pandatool/src/pstatserver/pStatTimeline.h @@ -0,0 +1,138 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatTimeline.h + * @author rdb + * @date 2022-02-11 + */ + +#ifndef PSTATTIMELINE_H +#define PSTATTIMELINE_H + +#include "pandatoolbase.h" + +#include "pStatGraph.h" +#include "pStatMonitor.h" +#include "pStatClientData.h" +#include "pdeque.h" + +class PStatFrameData; + +/** + * This is an abstract class that presents the interface for drawing a piano- + * roll type chart: it shows the time spent in each of a number of collectors + * as a horizontal bar of color, with time as the horizontal axis. + * + * This class just pnages all the piano-roll logic; the actual nuts and bolts + * of drawing pixels is left to a user-derived class. + */ +class PStatTimeline : public PStatGraph { +public: + PStatTimeline(PStatMonitor *monitor, int xsize, int ysize); + virtual ~PStatTimeline(); + + void new_data(int thread_index, int frame_number); + bool update_bars(int thread_index, int frame_number); + + INLINE void set_horizontal_scale(double time_width); + INLINE double get_horizontal_scale() const; + INLINE void set_horizontal_scroll(double time_start); + INLINE double get_horizontal_scroll() const; + + INLINE void zoom_to(double time_width, double pivot); + INLINE void zoom_by(double amount, double center); + INLINE void scroll_to(double time_start); + INLINE void scroll_by(double time_start); + + INLINE int timestamp_to_pixel(double time) const; + INLINE double pixel_to_timestamp(int x) const; + INLINE int height_to_pixel(double value) const; + INLINE double pixel_to_height(int y) const; + INLINE int get_num_rows() const; + + std::string get_bar_tooltip(int row, int x) const; + + virtual void write_datagram(Datagram &dg) const final; + virtual void read_datagram(DatagramIterator &scan) final; + +protected: + void changed_size(int xsize, int ysize); + void force_redraw(); + void force_redraw(int row, int from_x, int to_x); + void normal_guide_bars(); + + virtual void clear_region(); + virtual void begin_draw(); + void draw_thread(int thread_index, double start_time, double end_time); + void draw_row(int thread_index, int row_index, double start_time, double end_time); + virtual void draw_separator(int row); + virtual void draw_guide_bar(int x, GuideBarStyle style); + virtual void draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name); + virtual void end_draw(); + virtual void idle(); + + bool animate(double time, double dt); + + class ColorBar { + public: + double _start, _end; + int _collector_index; + int _thread_index; + int _frame_number; + bool _open_begin : 8, _open_end : 8; + + bool operator < (const ColorBar &other) const { + return _end < other._end; + } + }; + typedef pvector Row; + typedef pvector Rows; + + bool find_bar(int row, int x, ColorBar &bar) const; + + class ThreadRow { + public: + std::string _label; + Rows _rows; + size_t _row_offset = 0; + int _last_frame = -1; + bool _visible = false; + }; + typedef pvector ThreadRows; + ThreadRows _threads; + bool _threads_changed = true; + double _clock_skew = 0.0; + int _app_collector_index = -1; + + enum KeyFlag { + F_left = 1, + F_right = 2, + F_w = 4, + F_a = 8, + F_s = 16, + F_d = 32, + }; + int _keys_held = 0; + double _scroll_speed = 0.0; + double _zoom_speed = 0.0; + double _zoom_center = 0.0; + +private: + double _time_scale; + double _start_time = 0.0; + double _lowest_start_time = 0.0; + double _highest_end_time = 0.0; + bool _have_start_time = false; + double _target_start_time = 0.0; + double _target_time_scale; +}; + +#include "pStatTimeline.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatView.I b/pandatool/src/pstatserver/pStatView.I new file mode 100644 index 00000000..3066ba0f --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.I @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatView.I + * @author drose + * @date 2000-07-12 + */ + +/** + * Returns the current PStatThreadData associated with the view. This was set + * by a previous call to set_thread_data(). + */ +INLINE const PStatThreadData *PStatView:: +get_thread_data() { + return _thread_data; +} + +/** + * Returns the current PStatClientData associated with the view. This was + * also set by a previous call to set_thread_data(). + */ +INLINE const PStatClientData *PStatView:: +get_client_data() { + return _client_data; +} + + +/** + * Sets to a particular frame number (or the nearest available), extracted + * from the View's PStatThreadData pointer. See the comments in the other + * flavor of set_to_frame(). + */ +INLINE void PStatView:: +set_to_frame(int frame_number) { + set_to_frame(_thread_data->get_frame(frame_number)); +} + +/** + * Sets to the frame that occurred at the indicated time (or the nearest + * available frame), extracted from the View's PStatThreadData pointer. See + * the comments in set_to_frame. + */ +INLINE void PStatView:: +set_to_time(double time) { + set_to_frame(_thread_data->get_frame_at_time(time)); +} + +/** + * Returns true if we are showing level data, false if time data. + */ +INLINE bool PStatView:: +get_show_level() const { + return _show_level; +} + +/** + * Returns an index number that can be used to determine when the set of known + * levels has changed. Each time the set of levels in the view changes + * (because of new data arriving from the client, for instance), this number + * is incremented. + */ +INLINE int PStatView:: +get_level_index() const { + return _level_index; +} diff --git a/pandatool/src/pstatserver/pStatView.cxx b/pandatool/src/pstatserver/pStatView.cxx new file mode 100644 index 00000000..c72213bf --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.cxx @@ -0,0 +1,550 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatView.cxx + * @author drose + * @date 2000-07-10 + */ + +#include "pStatView.h" + +#include "pStatFrameData.h" +#include "pStatCollectorDef.h" + +#include +#include + +/** + * This class is used within this module only--in fact, within + * PStatView::set_to_frame() only--to help collect event data out of the + * PStatFrameData object and boil it down to a list of elapsed times. + */ +class FrameSample { +public: + void start(double time, FrameSample *started) { + // Keep track of nested start/stop pairs. We only consider the outer one. + if (_started++ > 0) { + return; + } + + nassertv(!_pushed); + _net_time -= time; + push_all(time, started); + nassertv(_next == nullptr && _prev == nullptr); + _prev = started->_prev; + _next = started; + _prev->_next = this; + started->_prev = this; + } + + void stop(double time, FrameSample *started) { + nassertv(_started > 0); + if (--_started > 0) { + return; + } + + nassertv(_next != nullptr && _prev != nullptr); + + if (_pushed) { + _prev->_next = _next; + _next->_prev = _prev; + _next = _prev = nullptr; + } else { + _net_time += time; + _prev->_next = _next; + _next->_prev = _prev; + _next = _prev = nullptr; + pop_one(time, started); + } + } + +private: + void push(double time) { + if (!_pushed) { + _pushed = true; + if (_started > 0) { + _net_time += time; + } + } + } + + void pop(double time) { + if (_pushed) { + _pushed = false; + if (_started > 0) { + _net_time -= time; + } + } + } + + void push_all(double time, FrameSample *started) { + for (FrameSample *sample = started->_next; + sample != started; sample = sample->_next) { + sample->push(time); + } + } + + void pop_one(double time, FrameSample *started) { + for (FrameSample *sample = started->_prev; + sample != started; sample = sample->_prev) { + if (sample->_pushed) { + sample->pop(time); + return; + } + } + } + +public: + FrameSample *_next = nullptr; + FrameSample *_prev = nullptr; + double _net_time = 0.0; + int _started = 0; + int _count = 0; + bool _pushed = false; + bool _is_new = false; +}; + +/** + * + */ +PStatView:: +PStatView() { + _constraint = 0; + _show_level = false; + _all_collectors_known = false; + _level_index = 0; +} + +/** + * + */ +PStatView:: +~PStatView() { + clear_levels(); +} + +/** + * Changes the focus of the View. By default, the View reports the entire + * time for the frame, and all of the Collectors that are directly parented to + * "Frame". By constraining the view to a particular collector, you cause the + * View to zoom in on that collector's data, reporting only the collector and + * its immediate parents. + * + * When you constrain the view, you may also specify whether the view should + * show time data or level data for the indicated collector. If level data, + * it reports the levels for the collector, and all of its children; + * otherwise, it collects the elapsed time. + * + * Changing the constraint causes the current frame's data to become + * invalidated; you must then call set_to_frame() again to get any useful data + * out. + */ +void PStatView:: +constrain(int collector, bool show_level) { + _constraint = collector; + _show_level = show_level; + clear_levels(); +} + +/** + * Restores the view to the full frame. This is equivalent to calling + * constrain(0). + */ +void PStatView:: +unconstrain() { + constrain(0, false); +} + +/** + * + */ +void PStatView:: +set_thread_data(const PStatThreadData *thread_data) { + _thread_data = thread_data; + _client_data = thread_data->get_client_data(); + clear_levels(); + _all_collectors_known = false; +} + +/** + * Supplies the View with the data for the current frame. This causes the + * View to update all of its internal data to reflect the frame's data, + * subject to the current constraint. + * + * It is possible that calling this will increase the total number of reported + * levels (for instance, if this frame introduced a new collector that hadn't + * been active previously). In this case, the caller must update its display + * or whatever to account for the new level. + */ +void PStatView:: +set_to_frame(const PStatFrameData &frame_data) { + nassertv(!_thread_data.is_null()); + nassertv(!_client_data.is_null()); + + if (_show_level) { + update_level_data(frame_data); + } else { + update_time_data(frame_data); + } +} + + +/** + * After a call to set_to_frame(), this returns true if all collectors in the + * FrameData are known by the PStatsData object, or false if some are still + * unknown (even those that do not appear in the view). + */ +bool PStatView:: +all_collectors_known() const { + return _all_collectors_known; +} + +/** + * Returns the total value accounted for by the frame (or by whatever + * Collector we are constrained to). This is the sum of all of the individual + * levels' get_net_value() value. + */ +double PStatView:: +get_net_value() const { + double net = 0.0; + Levels::const_iterator li; + for (li = _levels.begin(); li != _levels.end(); ++li) { + net += (*li).second->_value_alone; + } + + return net; +} + +/** + * Returns a pointer to the level that corresponds to the Collector we've + * constrained to. This is the top of a graph of levels; typically the next + * level down--the children of this level--will be the levels you want to + * display to the user. + */ +const PStatViewLevel *PStatView:: +get_top_level() { + return get_level(_constraint); +} + +/** + * Returns true if there is a level defined for the particular collector, + * false otherwise. + */ +bool PStatView:: +has_level(int collector) const { + Levels::const_iterator li; + li = _levels.find(collector); + return (li != _levels.end()); +} + +/** + * Returns a pointer to the level that corresponds to the indicated Collector. + * If there is no such level in the view, one will be created--use with + * caution. Check has_level() first if you don't want this behavior. + */ +PStatViewLevel *PStatView:: +get_level(int collector) { + Levels::const_iterator li; + li = _levels.find(collector); + if (li != _levels.end()) { + return (*li).second; + } + + PStatViewLevel *level = new PStatViewLevel; + level->_collector = collector; + level->_parent = nullptr; + _levels[collector] = level; + + reset_level(level); + return level; +} + +/** + * The implementation of set_to_frame() for views that show elapsed time. + */ +void PStatView:: +update_time_data(const PStatFrameData &frame_data) { + int num_events = frame_data.get_num_events(); + int num_collectors = _client_data->get_num_collectors(); + + typedef std::vector Samples; + Samples samples(num_collectors); + + // Keep a linked list of started samples. + FrameSample started; + started._next = &started; + started._prev = &started; + + _all_collectors_known = true; + + int new_collectors = 0; + int i; + for (i = 0; i < num_events; i++) { + int collector_index = frame_data.get_time_collector(i); + bool is_start = frame_data.is_start(i); + + if (!_client_data->has_collector(collector_index)) { + _all_collectors_known = false; + + } else { + nassertv(collector_index >= 0 && collector_index < num_collectors); + + if (_client_data->get_child_distance(_constraint, collector_index) >= 0) { + // Here's a data point we care about: anything at constraint level or + // below. + if (is_start) { + samples[collector_index].start(frame_data.get_time(i), &started); + samples[collector_index]._count++; + } else { + // A "stop" in the middle of a frame implies a "start" since time + // 0 (that is, since the first data point in the frame). + if (samples[collector_index]._started == 0) { + samples[collector_index].start(frame_data.get_time(0), &started); + } + samples[collector_index].stop(frame_data.get_time(i), &started); + } + + if (!samples[collector_index]._is_new) { + samples[collector_index]._is_new = true; + ++new_collectors; + } + } + } + } + + // Make sure everything is stopped. + Samples::iterator si; + for (i = 0, si = samples.begin(); si != samples.end(); ++i, ++si) { + if ((*si)._started > 0) { + (*si).stop(frame_data.get_end(), &started); + } + } + + nassertv(started._next == &started && started._prev == &started); + + bool any_new_levels = false; + + // Now match these samples we got up with those we already had in the + // levels. + Levels::iterator li, lnext; + li = _levels.begin(); + while (li != _levels.end()) { + // Be careful while traversing a container and calling functions that + // could modify that container. + lnext = li; + ++lnext; + + PStatViewLevel *level = (*li).second; + if (reset_level(level)) { + any_new_levels = true; + } + + int collector_index = level->_collector; + if (samples[collector_index]._is_new) { + level->_value_alone = samples[collector_index]._net_time; + level->_count = samples[collector_index]._count; + samples[collector_index]._is_new = false; + --new_collectors; + } + + li = lnext; + } + + // Finally, any samples left over in the got_samples set are new collectors + // that we need to add to the Levels list. + if (new_collectors > 0) { + any_new_levels = true; + + for (int collector_index = 0; collector_index < num_collectors; ++collector_index) { + if (samples[collector_index]._is_new) { + PStatViewLevel *level = get_level(collector_index); + level->_value_alone = samples[collector_index]._net_time; + level->_count = samples[collector_index]._count; + } + } + } + + if (any_new_levels) { + _level_index++; + } +} + +/** + * The implementation of set_to_frame() for views that show level values. + */ +void PStatView:: +update_level_data(const PStatFrameData &frame_data) { + _all_collectors_known = true; + + + // This tracks the set of level values we got. + typedef pmap GotValues; + GotValues net_values; + + int i; + int num_levels = frame_data.get_num_levels(); + for (i = 0; i < num_levels; i++) { + int collector_index = frame_data.get_level_collector(i); + double value = frame_data.get_level(i); + + if (!_client_data->has_collector(collector_index)) { + _all_collectors_known = false; + + } else { + if (_client_data->get_child_distance(_constraint, collector_index) >= 0) { + net_values[collector_index] = value; + } + } + } + + // Now that we've counted up the net level for each collector, compute the + // level for each collector alone by subtracting out each child from its + // parents. If a parent has no data, nothing is subtracted. + GotValues alone_values = net_values; + + GotValues::iterator gi; + for (gi = net_values.begin(); gi != net_values.end(); ++gi) { + int collector_index = (*gi).first; + double value = (*gi).second; + + // Walk up to the top, but stop when we find a parent with actual data. + while (collector_index != 0 && collector_index != _constraint) { + const PStatCollectorDef &def = + _client_data->get_collector_def(collector_index); + int parent_index = def._parent_index; + GotValues::iterator pi = alone_values.find(parent_index); + if (pi != alone_values.end()) { + // The parent has data; subtract it. + (*pi).second -= value; + break; + } + collector_index = parent_index; + } + } + + + bool any_new_levels = false; + + // Now match these samples we got up with those we already had in the + // levels. + Levels::iterator li, lnext; + li = _levels.begin(); + while (li != _levels.end()) { + // Be careful while traversing a container and calling functions that + // could modify that container. + lnext = li; + ++lnext; + + PStatViewLevel *level = (*li).second; + if (reset_level(level)) { + any_new_levels = true; + } + + int collector_index = level->_collector; + GotValues::iterator gi; + gi = alone_values.find(collector_index); + if (gi != alone_values.end()) { + level->_value_alone = (*gi).second; + alone_values.erase(gi); + } + + li = lnext; + } + + // Finally, any values left over in the alone_values set are new collectors + // that we need to add to the Levels list. + if (!alone_values.empty()) { + any_new_levels = true; + + GotValues::const_iterator gi; + for (gi = alone_values.begin(); gi != alone_values.end(); ++gi) { + int collector_index = (*gi).first; + PStatViewLevel *level = get_level(collector_index); + level->_value_alone = (*gi).second; + } + } + + if (any_new_levels) { + _level_index++; + } +} + +/** + * Resets all the levels that have been defined so far. + */ +void PStatView:: +clear_levels() { + Levels::iterator li; + for (li = _levels.begin(); li != _levels.end(); ++li) { + delete (*li).second; + } + _levels.clear(); +} + +/** + * Resets the total value of the Level to zero, and also makes sure it is + * parented to the right Level corresponding to its Collector's parent. Since + * the client might change its mind from time to time about who the Collector + * is parented to, we have to update this dynamically. + * + * Returns true if any change was made to the level's hierarchy, false + * otherwise. + */ +bool PStatView:: +reset_level(PStatViewLevel *level) { + bool any_changed = false; + level->_value_alone = 0.0; + level->_count = 0; + + if (level->_collector == _constraint) { + return false; + } + + if (_client_data->has_collector(level->_collector)) { + int parent_index = + _client_data->get_collector_def(level->_collector)._parent_index; + + if (level->_parent == nullptr) { + // This level didn't know its parent before, but now it does. + if (level->_collector != 0 || parent_index != 0) { + PStatViewLevel *parent_level = get_level(parent_index); + nassertr(parent_level != level, true); + level->_parent = parent_level; + parent_level->_children.push_back(level); + parent_level->sort_children(_client_data); + any_changed = true; + } + } + else if (level->_parent->_collector != parent_index) { + // This level knew about its parent, but now it's something different. + PStatViewLevel *old_parent_level = level->_parent; + nassertr(old_parent_level != level, true); + + if (parent_index != 0) { + PStatViewLevel *new_parent_level = get_level(parent_index); + nassertr(new_parent_level != level, true); + level->_parent = new_parent_level; + new_parent_level->_children.push_back(level); + new_parent_level->sort_children(_client_data); + } else { + level->_parent = nullptr; + } + + PStatViewLevel::Children::iterator ci = + find(old_parent_level->_children.begin(), + old_parent_level->_children.end(), + level); + + nassertr(ci != old_parent_level->_children.end(), true); + old_parent_level->_children.erase(ci); + any_changed = true; + } + } + + return any_changed; +} diff --git a/pandatool/src/pstatserver/pStatView.h b/pandatool/src/pstatserver/pStatView.h new file mode 100644 index 00000000..3bae72b1 --- /dev/null +++ b/pandatool/src/pstatserver/pStatView.h @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatView.h + * @author drose + * @date 2000-07-10 + */ + +#ifndef PSTATVIEW_H +#define PSTATVIEW_H + +#include "pandatoolbase.h" + +#include "pStatClientData.h" +#include "pStatThreadData.h" +#include "pStatViewLevel.h" +#include "pmap.h" +#include "pointerTo.h" + +/** + * A View boils down the frame data to a linear list of times spent in a + * number of different Collectors, within a particular thread. This + * automatically accounts for overlapping start/stop times and nested + * Collectors in a sensible way. + */ +class PStatView { +public: + PStatView(); + ~PStatView(); + + void constrain(int collector, bool show_level); + void unconstrain(); + + void set_thread_data(const PStatThreadData *thread_data); + INLINE const PStatThreadData *get_thread_data(); + INLINE const PStatClientData *get_client_data(); + + void set_to_frame(const PStatFrameData &frame_data); + INLINE void set_to_frame(int frame_number); + INLINE void set_to_time(double time); + + bool all_collectors_known() const; + double get_net_value() const; + + const PStatViewLevel *get_top_level(); + + bool has_level(int collector) const; + PStatViewLevel *get_level(int collector); + + INLINE bool get_show_level() const; + INLINE int get_level_index() const; + +private: + void update_time_data(const PStatFrameData &frame_data); + void update_level_data(const PStatFrameData &frame_data); + + void clear_levels(); + bool reset_level(PStatViewLevel *level); + + int _constraint; + bool _show_level; + bool _all_collectors_known; + + typedef pmap Levels; + Levels _levels; + + int _level_index; + + CPT(PStatClientData) _client_data; + CPT(PStatThreadData) _thread_data; +}; + +#include "pStatView.I" + +#endif diff --git a/pandatool/src/pstatserver/pStatViewLevel.I b/pandatool/src/pstatserver/pStatViewLevel.I new file mode 100644 index 00000000..38fdec95 --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.I @@ -0,0 +1,37 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatViewLevel.I + * @author drose + * @date 2000-07-19 + */ + +/** + * Returns the Collector index associated with this level. + */ +INLINE int PStatViewLevel:: +get_collector() const { + return _collector; +} + +/** + * Returns the total level value (or elapsed time value) for this Collector, + * not including any values accounted for by its child Collectors. + */ +INLINE double PStatViewLevel:: +get_value_alone() const { + return _value_alone; +} + +/** + * Returns the number of start/stop pairs for this collector. + */ +INLINE int PStatViewLevel:: +get_count() const { + return _count; +} diff --git a/pandatool/src/pstatserver/pStatViewLevel.cxx b/pandatool/src/pstatserver/pStatViewLevel.cxx new file mode 100644 index 00000000..47ed1fae --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.cxx @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatViewLevel.cxx + * @author drose + * @date 2000-07-11 + */ + +#include "pStatViewLevel.h" +#include "pStatClientData.h" + +#include "pStatCollectorDef.h" +#include "pnotify.h" + +#include + +/** + * Returns the total level value (or elapsed time) represented by this + * Collector, including all values in its child Collectors. + */ +double PStatViewLevel:: +get_net_value() const { + double net = _value_alone; + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + net += (*ci)->get_net_value(); + } + + return net; +} + + +// STL function object for sorting children in order by the collector's sort +// index, used in sort_children(), below. +class SortCollectorLevels { +public: + SortCollectorLevels(const PStatClientData *client_data) : + _client_data(client_data) { + } + bool operator () (const PStatViewLevel *a, const PStatViewLevel *b) const { + return + _client_data->get_collector_def(a->get_collector())._sort > + _client_data->get_collector_def(b->get_collector())._sort; + } + const PStatClientData *_client_data; +}; + +/** + * Sorts the children of this view level into order as specified by the + * client's sort index. + */ +void PStatViewLevel:: +sort_children(const PStatClientData *client_data) { + SortCollectorLevels sort_levels(client_data); + + sort(_children.begin(), _children.end(), sort_levels); +} + +/** + * Returns the number of children of this Level/Collector. These are the + * Collectors whose value is considered to be part of the total value of this + * level's Collector. + */ +int PStatViewLevel:: +get_num_children() const { + return _children.size(); +} + +/** + * Returns the nth child of this Level/Collector. + */ +const PStatViewLevel *PStatViewLevel:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), nullptr); + return _children[n]; +} diff --git a/pandatool/src/pstatserver/pStatViewLevel.h b/pandatool/src/pstatserver/pStatViewLevel.h new file mode 100644 index 00000000..802850c0 --- /dev/null +++ b/pandatool/src/pstatserver/pStatViewLevel.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pStatViewLevel.h + * @author drose + * @date 2000-07-11 + */ + +#ifndef PSTATVIEWLEVEL_H +#define PSTATVIEWLEVEL_H + +#include "pandatoolbase.h" + +#include "pvector.h" + +class PStatClientData; + +/** + * This is a single level value, or band of color, within a View. + * + * It generally indicates either the elapsed time, or the "level" value, for a + * particular Collector within a given frame for a particular thread. + */ +class PStatViewLevel { +public: + INLINE int get_collector() const; + INLINE double get_value_alone() const; + double get_net_value() const; + INLINE int get_count() const; + + void sort_children(const PStatClientData *client_data); + + int get_num_children() const; + const PStatViewLevel *get_child(int n) const; + +private: + int _collector; + int _count = 0; + double _value_alone; + PStatViewLevel *_parent; + + typedef pvector Children; + Children _children; + + friend class PStatView; +}; + +#include "pStatViewLevel.I" + +#endif diff --git a/pandatool/src/ptloader/CMakeLists.txt b/pandatool/src/ptloader/CMakeLists.txt new file mode 100644 index 00000000..f4d4ab9f --- /dev/null +++ b/pandatool/src/ptloader/CMakeLists.txt @@ -0,0 +1,31 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3PTLOADER_HEADERS + config_ptloader.h + loaderFileTypePandatool.h +) + +set(P3PTLOADER_SOURCES + config_ptloader.cxx + loaderFileTypePandatool.cxx +) + +composite_sources(p3ptloader P3PTLOADER_SOURCES) +add_library(p3ptloader ${MODULE_TYPE} ${P3PTLOADER_HEADERS} ${P3PTLOADER_SOURCES}) +set_target_properties(p3ptloader PROPERTIES DEFINE_SYMBOL BUILDING_PTLOADER) +target_link_libraries(p3ptloader PRIVATE + p3dxfegg p3fltegg p3lwoegg p3vrmlegg p3xfileegg + p3converter) + +if(HAVE_FCOLLADA) + target_link_libraries(p3ptloader PRIVATE p3daeegg) + target_compile_definitions(p3ptloader PRIVATE HAVE_FCOLLADA) +endif() + +if(BUILD_SHARED_LIBS) + # We can't install this if we're doing a static build, because it depends on + # a bunch of static libraries that aren't installed. + install(TARGETS p3ptloader EXPORT Tools COMPONENT Tools DESTINATION ${MODULE_DESTINATION}) +endif() diff --git a/pandatool/src/ptloader/config_ptloader.cxx b/pandatool/src/ptloader/config_ptloader.cxx new file mode 100644 index 00000000..a9c4b54d --- /dev/null +++ b/pandatool/src/ptloader/config_ptloader.cxx @@ -0,0 +1,116 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_ptloader.cxx + * @author drose + * @date 2001-04-26 + */ + +#include "config_ptloader.h" + +// This needs to be included first to work around a bug in OSX 10.4. +#if defined(HAVE_FCOLLADA) && defined(IS_OSX) +#include "daeToEggConverter.h" +#endif + +#include "loaderFileTypePandatool.h" + +#include "config_flt.h" +#include "fltToEggConverter.h" +#include "config_lwo.h" +#include "lwoToEggConverter.h" +#include "dxfToEggConverter.h" +#include "vrmlToEggConverter.h" +//#include "objToEggConverter.h" +//#include "eggToObjConverter.h" +#include "config_xfile.h" +#include "xFileToEggConverter.h" + +// Windows freaks out if this input is placed earlier. +#if defined(HAVE_FCOLLADA) && !defined(IS_OSX) +#include "daeToEggConverter.h" +#endif + +#include "dconfig.h" +#include "loaderFileTypeRegistry.h" +#include "eggData.h" + +ConfigureDef(config_ptloader); +NotifyCategoryDef(ptloader, ""); + +ConfigureFn(config_ptloader) { + init_libptloader(); +} + +ConfigVariableEnum ptloader_units +("ptloader-units", DU_invalid, + PRC_DESC("Specifies the preferred units into which models will be converted " + "when using libptloader to automatically convert files to Panda " + "at load time, via e.g. \"pview myMayaFile.mb\".")); + +ConfigVariableBool ptloader_load_node +("ptloader-load-node", true, + PRC_DESC("Specify true to allow libptloader to invoke the more efficient " + "but possibly-experimental code to load model files directly into " + "PandaNode when possible. Specify false to force the loading to " + "always go through the egg library, which is more likely to be " + "reliable.")); + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libptloader() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + LoaderFileTypePandatool::init_type(); + + LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr(); + + init_liblwo(); + init_libflt(); + + FltToEggConverter *flt = new FltToEggConverter; + reg->register_type(new LoaderFileTypePandatool(flt)); + + LwoToEggConverter *lwo = new LwoToEggConverter; + reg->register_type(new LoaderFileTypePandatool(lwo)); + + DXFToEggConverter *dxf = new DXFToEggConverter; + reg->register_type(new LoaderFileTypePandatool(dxf)); + + VRMLToEggConverter *vrml = new VRMLToEggConverter; + reg->register_type(new LoaderFileTypePandatool(vrml)); + + init_libxfile(); + XFileToEggConverter *xfile = new XFileToEggConverter; + reg->register_type(new LoaderFileTypePandatool(xfile)); + + //ObjToEggConverter *obj_egg = new ObjToEggConverter; + //EggToObjConverter *egg_obj = new EggToObjConverter; + //reg->register_type(new LoaderFileTypePandatool(obj_egg, egg_obj)); + +// #ifdef HAVE_FCOLLADA DAEToEggConverter *dae = new DAEToEggConverter; +// reg->register_type(new LoaderFileTypePandatool(dae)); #endif + +#ifdef HAVE_MAYA + // Register the Maya converter as a deferred type. We don't compile it in + // directly, because it's big and bulky; we don't need to force people to + // load up libmayaloader (and, along with it, all of the Maya API libraries) + // until they actually try to load a Maya file. + reg->register_deferred_type("mb", "mayaloader"); + reg->register_deferred_type("ma", "mayaloader"); +#endif +} diff --git a/pandatool/src/ptloader/config_ptloader.h b/pandatool/src/ptloader/config_ptloader.h new file mode 100644 index 00000000..fc324dc9 --- /dev/null +++ b/pandatool/src/ptloader/config_ptloader.h @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_ptloader.h + * @author drose + * @date 2001-04-26 + */ + +#ifndef CONFIG_PTLOADER_H +#define CONFIG_PTLOADER_H + +#include "pandatoolbase.h" +#include "notifyCategoryProxy.h" +#include "dconfig.h" +#include "distanceUnit.h" +#include "configVariableEnum.h" +#include "configVariableBool.h" + +ConfigureDecl(config_ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER); +NotifyCategoryDecl(ptloader, EXPCL_PTLOADER, EXPTP_PTLOADER); + +extern ConfigVariableEnum ptloader_units; +extern ConfigVariableBool ptloader_load_node; + +extern EXPCL_PTLOADER void init_libptloader(); + +#endif diff --git a/pandatool/src/ptloader/loaderFileTypePandatool.cxx b/pandatool/src/ptloader/loaderFileTypePandatool.cxx new file mode 100644 index 00000000..9536b3fe --- /dev/null +++ b/pandatool/src/ptloader/loaderFileTypePandatool.cxx @@ -0,0 +1,223 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file loaderFileTypePandatool.cxx + * @author drose + * @date 2001-04-26 + */ + +#include "loaderFileTypePandatool.h" +#include "config_ptloader.h" +#include "somethingToEggConverter.h" +#include "eggToSomethingConverter.h" +#include "config_putil.h" +#include "load_egg_file.h" +#include "save_egg_file.h" +#include "eggData.h" +#include "loaderOptions.h" +#include "bamCacheRecord.h" + +TypeHandle LoaderFileTypePandatool::_type_handle; + +/** + * + */ +LoaderFileTypePandatool:: +LoaderFileTypePandatool(SomethingToEggConverter *loader, + EggToSomethingConverter *saver) : + _loader(loader), _saver(saver) +{ + if (_loader != nullptr) { + _loader->set_merge_externals(true); + } +} + +/** + * + */ +LoaderFileTypePandatool:: +~LoaderFileTypePandatool() { +} + +/** + * + */ +std::string LoaderFileTypePandatool:: +get_name() const { + if (_loader != nullptr) { + return _loader->get_name(); + } + return _saver->get_name(); +} + +/** + * + */ +std::string LoaderFileTypePandatool:: +get_extension() const { + if (_loader != nullptr) { + return _loader->get_extension(); + } + return _saver->get_extension(); +} + +/** + * Returns a space-separated list of extension, in addition to the one + * returned by get_extension(), that are recognized by this converter. + */ +std::string LoaderFileTypePandatool:: +get_additional_extensions() const { + if (_loader != nullptr) { + return _loader->get_additional_extensions(); + } + return _saver->get_additional_extensions(); +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz or .gz extension), false otherwise. + */ +bool LoaderFileTypePandatool:: +supports_compressed() const { + if (_loader != nullptr) { + return _loader->supports_compressed(); + } + return _saver->supports_compressed(); +} + +/** + * Returns true if the file type can be used to load files, and load_file() is + * supported. Returns false if load_file() is unimplemented and will always + * fail. + */ +bool LoaderFileTypePandatool:: +supports_load() const { + return (_loader != nullptr); +} + +/** + * Returns true if the file type can be used to save files, and save_file() is + * supported. Returns false if save_file() is unimplemented and will always + * fail. + */ +bool LoaderFileTypePandatool:: +supports_save() const { + return (_saver != nullptr); +} + +/** + * Searches for the indicated filename on whatever paths are appropriate to + * this file type, and updates it if it is found. + */ +void LoaderFileTypePandatool:: +resolve_filename(Filename &path) const { + path.resolve_filename(get_model_path(), get_extension()); +} + +/** + * + */ +PT(PandaNode) LoaderFileTypePandatool:: +load_file(const Filename &path, const LoaderOptions &options, + BamCacheRecord *record) const { + if (_loader == nullptr) { + return nullptr; + } + + if (record != nullptr) { + record->add_dependent_file(path); + } + + PT(PandaNode) result; + + SomethingToEggConverter *loader = _loader->make_copy(); + + DSearchPath file_path; + file_path.append_directory(path.get_dirname()); + loader->get_path_replace()->_path = file_path; + + // Convert animation, if the converter supports it. + switch (options.get_flags() & LoaderOptions::LF_convert_anim) { + case LoaderOptions::LF_convert_anim: + loader->set_animation_convert(AC_both); + break; + + case LoaderOptions::LF_convert_skeleton: + loader->set_animation_convert(AC_model); + break; + + case LoaderOptions::LF_convert_channels: + loader->set_animation_convert(AC_chan); + break; + + default: + break; + } + + // Try to convert directly to PandaNode first, if the converter type + // supports it. + if (ptloader_load_node && loader->supports_convert_to_node(options)) { + result = loader->convert_to_node(options, path); + if (!result.is_null()) { + return result; + } + } + + // If the converter type doesn't support the direct PandaNode conversion, + // take the slower route through egg instead. + PT(EggData) egg_data = new EggData; + loader->set_egg_data(egg_data); + + if (loader->convert_file(path)) { + DistanceUnit input_units = loader->get_input_units(); + if (input_units != DU_invalid && ptloader_units != DU_invalid && + input_units != ptloader_units) { + // Convert the file to the units specified by the ptloader-units + // Configrc variable. + ptloader_cat.info() + << "Converting from " << format_long_unit(input_units) + << " to " << format_long_unit(ptloader_units) << "\n"; + double scale = convert_units(input_units, ptloader_units); + egg_data->transform(LMatrix4d::scale_mat(scale)); + } + + if (!egg_data->has_primitives()) { + egg_data->make_point_primitives(); + } else if (!egg_data->has_normals()) { + egg_data->recompute_polygon_normals(); + } + + result = load_egg_data(egg_data); + } + delete loader; + + return result; +} + +/** + * + */ +bool LoaderFileTypePandatool:: +save_file(const Filename &path, const LoaderOptions &options, + PandaNode *node) const { + if (_saver == nullptr) { + return false; + } + + PT(EggData) egg_data = new EggData; + if (!save_egg_data(egg_data, node)) { + return false; + } + + EggToSomethingConverter *saver = _saver->make_copy(); + saver->set_egg_data(egg_data); + + bool result = saver->write_file(path); + delete saver; + return result; +} diff --git a/pandatool/src/ptloader/loaderFileTypePandatool.h b/pandatool/src/ptloader/loaderFileTypePandatool.h new file mode 100644 index 00000000..f50400cd --- /dev/null +++ b/pandatool/src/ptloader/loaderFileTypePandatool.h @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file loaderFileTypePandatool.h + * @author drose + * @date 2000-06-20 + */ + +#ifndef LOADERFILETYPEPANDATOOL_H +#define LOADERFILETYPEPANDATOOL_H + +#include "pandatoolbase.h" + +#include "loaderFileType.h" + +class SomethingToEggConverter; +class EggToSomethingConverter; + +/** + * This defines the Loader interface to files whose converters are defined + * within the Pandatool package and inherit from SomethingToEggConverter, like + * FltToEggConverter and LwoToEggConverter. + */ +class EXPCL_PTLOADER LoaderFileTypePandatool : public LoaderFileType { +public: + LoaderFileTypePandatool(SomethingToEggConverter *loader, + EggToSomethingConverter *saver = nullptr); + virtual ~LoaderFileTypePandatool(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual std::string get_additional_extensions() const; + virtual bool supports_compressed() const; + + virtual bool supports_load() const; + virtual bool supports_save() const; + + virtual void resolve_filename(Filename &path) const; + virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options, + BamCacheRecord *record) const; + virtual bool save_file(const Filename &path, const LoaderOptions &options, + PandaNode *node) const; + +private: + SomethingToEggConverter *_loader; + EggToSomethingConverter *_saver; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + LoaderFileType::init_type(); + register_type(_type_handle, "LoaderFileTypePandatool", + LoaderFileType::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif diff --git a/pandatool/src/text-stats/CMakeLists.txt b/pandatool/src/text-stats/CMakeLists.txt new file mode 100644 index 00000000..a2c976d2 --- /dev/null +++ b/pandatool/src/text-stats/CMakeLists.txt @@ -0,0 +1,23 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT HAVE_NET) + return() +endif() + +set(TEXTSTATS_HEADERS + textMonitor.h textMonitor.I + textStats.h +) + +set(TEXTSTATS_SOURCES + textMonitor.cxx + textStats.cxx +) + +composite_sources(text-stats TEXTSTATS_SOURCES) +add_executable(text-stats ${TEXTSTATS_HEADERS} ${TEXTSTATS_SOURCES}) +target_link_libraries(text-stats p3progbase p3pstatserver) + +install(TARGETS text-stats EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/text-stats/textMonitor.I b/pandatool/src/text-stats/textMonitor.I new file mode 100644 index 00000000..d5aba895 --- /dev/null +++ b/pandatool/src/text-stats/textMonitor.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textMonitor.I + * @author drose + * @date 2007-07-13 + */ diff --git a/pandatool/src/text-stats/textMonitor.cxx b/pandatool/src/text-stats/textMonitor.cxx new file mode 100644 index 00000000..acaa421d --- /dev/null +++ b/pandatool/src/text-stats/textMonitor.cxx @@ -0,0 +1,247 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textMonitor.cxx + * @author drose + * @date 2000-07-12 + */ + +#include "textMonitor.h" +#include "textStats.h" +#include "pStatCollectorDef.h" +#include "pStatFrameData.h" +#include "indent.h" +#include // sprintf + +/** + * + */ +TextMonitor:: +TextMonitor(TextStats *server, std::ostream *outStream, bool show_raw_data, bool json) : PStatMonitor(server) { + _outStream = outStream; //[PECI] + _show_raw_data = show_raw_data; + _json = json; +} + +/** + * Returns the server that owns this monitor. + */ +TextStats *TextMonitor:: +get_server() { + return (TextStats *)PStatMonitor::get_server(); +} + +/** + * Should be redefined to return a descriptive name for the type of + * PStatsMonitor this is. + */ +std::string TextMonitor:: +get_monitor_name() { + return "Text Stats"; +} + +/** + * Called when the "hello" message has been received from the client. At this + * time, the client's hostname and program name will be known. + */ +void TextMonitor:: +got_hello() { + nout << "Now connected to " << get_client_progname() << " on host " + << get_client_hostname() << "\n"; +} + +/** + * Like got_hello(), this is called when the "hello" message has been received + * from the client. At this time, the client's hostname and program name will + * be known. However, the client appears to be an incompatible version and + * the connection will be terminated; the monitor should issue a message to + * that effect. + */ +void TextMonitor:: +got_bad_version(int client_major, int client_minor, + int server_major, int server_minor) { + nout + << "Rejected connection by " << get_client_progname() + << " from " << get_client_hostname() + << ". Client uses PStats version " + << client_major << "." << client_minor + << ", while server expects PStats version " + << server_major << "." << server_minor << ".\n"; +} + +/** + * Called whenever a new Thread definition is received from the client. + * Generally, the client will send all of its threads over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Thread definitions midstream. + */ +void TextMonitor:: +new_thread(int thread_index) { + if (_json) { + const PStatClientData *client_data = get_client_data(); + + int pid = get_client_pid(); + if (pid < 0) { + pid = _dummy_pid; + } + + (*_outStream) + << "{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":" << pid + << ",\"tid\":" << thread_index << ",\"args\":{\"name\":\"" + << client_data->get_thread_name(thread_index) << "\"}},\n"; + } +} + +/** + * Called as each frame's data is made available. There is no gurantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void TextMonitor:: +new_data(int thread_index, int frame_number) { + PStatView &view = get_view(thread_index); + const PStatThreadData *thread_data = view.get_thread_data(); + + view.set_to_frame(frame_number); + + if (true) { + const PStatClientData *client_data = get_client_data(); + + if (_json) { + int pid = get_client_pid(); + if (pid < 0) { + pid = _dummy_pid; + } + + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { + int collector_index = frame_data.get_time_collector(i); + (*_outStream) + << "{\"name\":\"" << client_data->get_collector_fullname(collector_index) + << "\",\"ts\":" << (uint64_t)(frame_data.get_time(i) * 1000000) + << ",\"ph\":\"" << (frame_data.is_start(i) ? 'B' : 'E') << "\"" + << ",\"tid\":" << thread_index << ",\"pid\":" << pid << "},\n"; + } + } + else { + (*_outStream) << "\rThread " + << client_data->get_thread_name(thread_index) + << " frame " << frame_number << ", " + << view.get_net_value() * 1000.0 << " ms (" + << thread_data->get_frame_rate() << " Hz):\n"; + + if (_show_raw_data) { + const PStatFrameData &frame_data = thread_data->get_frame(frame_number); + (*_outStream) << "raw data:\n"; + size_t num_events = frame_data.get_num_events(); + for (size_t i = 0; i < num_events; ++i) { + // The iomanipulators are much too clumsy. + char formatted[32]; + sprintf(formatted, "%15.06lf", frame_data.get_time(i)); + (*_outStream) << formatted; + + if (frame_data.is_start(i)) { + (*_outStream) << " start "; + } else { + (*_outStream) << " stop "; + } + + int collector_index = frame_data.get_time_collector(i); + (*_outStream) << client_data->get_collector_fullname(collector_index) << "\n"; + } + } + + const PStatViewLevel *level = view.get_top_level(); + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + show_ms(level->get_child(i), 2); + } + + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, thread_index)) { + + PStatView &level_view = get_level_view(collector, thread_index); + level_view.set_to_frame(frame_number); + const PStatViewLevel *level = level_view.get_top_level(); + show_level(level, 2); + } + } + } + } + _outStream->flush(); +} + + +/** + * Called whenever the connection to the client has been lost. This is a + * permanent state change. The monitor should update its display to represent + * this, and may choose to close down automatically. + */ +void TextMonitor:: +lost_connection() { + nout << "Lost connection.\n"; + ++_dummy_pid; +} + +/** + * Should be redefined to return true if this monitor class can handle running + * in a sub-thread. + * + * This is not related to the question of whether it can handle multiple + * different PStatThreadDatas; this is strictly a question of whether or not + * the monitor itself wants to run in a sub-thread. + */ +bool TextMonitor:: +is_thread_safe() { + return false; +} + +/** + * + */ +void TextMonitor:: +show_ms(const PStatViewLevel *level, int indent_level) { + int collector_index = level->get_collector(); + + const PStatClientData *client_data = get_client_data(); + const PStatCollectorDef &def = client_data->get_collector_def(collector_index); + + indent((*_outStream), indent_level) + << def._name << " = " << level->get_net_value() * 1000.0 << " ms\n" ; + + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + show_ms(level->get_child(i), indent_level + 2); + } +} + +/** + * + */ +void TextMonitor:: +show_level(const PStatViewLevel *level, int indent_level) { + int collector_index = level->get_collector(); + + const PStatClientData *client_data = get_client_data(); + const PStatCollectorDef &def = client_data->get_collector_def(collector_index); + + indent((*_outStream), indent_level) + << def._name << " = " << level->get_net_value() << " " + << def._level_units << "\n"; + + int num_children = level->get_num_children(); + for (int i = 0; i < num_children; i++) { + show_level(level->get_child(i), indent_level + 2); + } +} diff --git a/pandatool/src/text-stats/textMonitor.h b/pandatool/src/text-stats/textMonitor.h new file mode 100644 index 00000000..13cfa860 --- /dev/null +++ b/pandatool/src/text-stats/textMonitor.h @@ -0,0 +1,57 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textMonitor.h + * @author drose + * @date 2000-07-12 + */ + +#ifndef TEXTMONITOR_H +#define TEXTMONITOR_H + +#include "pandatoolbase.h" +#include "pStatMonitor.h" + +// [PECI] +#include +#include + +class TextStats; + +/** + * A simple, scrolling-text stats monitor. Guaranteed to compile on every + * platform. + */ +class TextMonitor : public PStatMonitor { +public: + TextMonitor(TextStats *server, std::ostream *outStream, bool show_raw_data, bool json = false); + TextStats *get_server(); + + virtual std::string get_monitor_name(); + + virtual void got_hello(); + virtual void got_bad_version(int client_major, int client_minor, + int server_major, int server_minor); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + virtual void lost_connection(); + virtual bool is_thread_safe(); + + void show_ms(const PStatViewLevel *level, int indent_level); + void show_level(const PStatViewLevel *level, int indent_level); + +private: + std::ostream *_outStream; //[PECI] + bool _show_raw_data; + bool _json; + int _dummy_pid = 0; +}; + +#include "textMonitor.I" + +#endif diff --git a/pandatool/src/text-stats/textStats.cxx b/pandatool/src/text-stats/textStats.cxx new file mode 100644 index 00000000..2aa927f3 --- /dev/null +++ b/pandatool/src/text-stats/textStats.cxx @@ -0,0 +1,120 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textStats.cxx + * @author drose + * @date 2000-07-12 + */ + +#include "textStats.h" +#include "textMonitor.h" + +#include "pStatServer.h" +#include "config_pstatclient.h" + +#include + +static bool user_interrupted = false; + +// This simple signal handler lets us know when the user has pressed +// control-C, so we can clean up nicely. +static void signal_handler(int) { + user_interrupted = true; +} + +/** + * + */ +TextStats:: +TextStats() { + set_program_brief("text-based PStats client"); + set_program_description + ("This is a simple PStats server that listens on a TCP port for a " + "connection from a PStatClient in a Panda player. It will then report " + "frame rate and timing information sent by the player."); + + add_option + ("p", "port", 0, + "Specify the TCP port to listen for connections on. By default, this " + "is taken from the pstats-port Config variable.", + &TextStats::dispatch_int, nullptr, &_port); + + add_option + ("r", "", 0, + "Show the raw frame data, in addition to boiling it down to a total " + "time per collector.", + &TextStats::dispatch_none, &_show_raw_data, nullptr); + + add_option + ("j", "", 0, + "Output data in JSON format.", + &TextStats::dispatch_none, &_json, nullptr); + + add_option + ("o", "filename", 0, + "Filename where to print. If not given then stderr is being used.", + &TextStats::dispatch_string, &_got_outputFileName, &_outputFileName); + + _outFile = nullptr; + _port = pstats_port; +} + + +/** + * + */ +PStatMonitor *TextStats:: +make_monitor(const NetAddress &address) { + + return new TextMonitor(this, _outFile, _show_raw_data, _json); +} + + +/** + * + */ +void TextStats:: +run() { + // Set up a global signal handler to catch Interrupt (Control-C) so we can + // clean up nicely if the user stops us. + signal(SIGINT, &signal_handler); + + if (!listen(_port)) { + nout << "Unable to open port.\n"; + exit(1); + } + + nout << "Listening for connections.\n"; + + if (_got_outputFileName) { + _outFile = new std::ofstream(_outputFileName.c_str(), std::ios::out | std::ios::trunc); + } else { + _outFile = &(nout); + } + + if (_json) { + (*_outFile) << "[\n"; + } + + main_loop(&user_interrupted); + nout << "Exiting.\n"; + + if (_json) { + // Remove the last comma. + _outFile->seekp(-3, std::ios::cur); + (*_outFile) << "\n]\n"; + } +} + + +int main(int argc, char *argv[]) { + TextStats prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/text-stats/textStats.h b/pandatool/src/text-stats/textStats.h new file mode 100644 index 00000000..92ce71a7 --- /dev/null +++ b/pandatool/src/text-stats/textStats.h @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file textStats.h + * @author drose + * @date 2000-07-12 + */ + +#ifndef TEXTSTATS_H +#define TEXTSTATS_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "pStatServer.h" + +#include +#include + +/** + * A simple, scrolling-text stats server. Guaranteed to compile on every + * platform. + */ +class TextStats : public ProgramBase, public PStatServer { +public: + TextStats(); + + virtual PStatMonitor *make_monitor(const NetAddress &address); + + void run(); + +private: + int _port; + bool _show_raw_data; + bool _json = false; + + // [PECI] + bool _got_outputFileName; + std::string _outputFileName; + std::ostream *_outFile; +}; + +#endif diff --git a/pandatool/src/vrml/CMakeLists.txt b/pandatool/src/vrml/CMakeLists.txt new file mode 100644 index 00000000..235ccbff --- /dev/null +++ b/pandatool/src/vrml/CMakeLists.txt @@ -0,0 +1,30 @@ +set(P3VRML_HEADERS + parse_vrml.h + standard_nodes.h + vrmlLexerDefs.h + vrmlNode.h + vrmlNodeType.h + vrmlParserDefs.h +) + +set(P3VRML_SOURCES + parse_vrml.cxx + standard_nodes.cxx + vrmlNode.cxx + vrmlNodeType.cxx +) + +set(P3VRML_PARSER_SOURCES + vrmlParser.cxx + vrmlLexer.cxx +) + +add_bison_target(vrmlParser.cxx vrmlParser.yxx DEFINES vrmlParser.h PREFIX vrmlyy) +add_flex_target(vrmlLexer.cxx vrmlLexer.lxx CASE_INSENSITIVE PREFIX vrmlyy) + +composite_sources(p3vrml P3VRML_SOURCES) +add_library(p3vrml STATIC ${P3VRML_HEADERS} ${P3VRML_SOURCES} ${P3VRML_PARSER_SOURCES}) +target_link_libraries(p3vrml p3pandatoolbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/vrml/parse_vrml.cxx b/pandatool/src/vrml/parse_vrml.cxx new file mode 100644 index 00000000..9ec700f1 --- /dev/null +++ b/pandatool/src/vrml/parse_vrml.cxx @@ -0,0 +1,140 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file parse_vrml.cxx + * @author drose + * @date 2004-10-01 + */ + +/************************************************** + * VRML 2.0, Draft 2 Parser + * Copyright (C) 1996 Silicon Graphics, Inc. + * + * Author(s) : Gavin Bell + * Daniel Woods (first port) + ************************************************** + */ + +#include "pandatoolbase.h" + +#include "parse_vrml.h" +#include "vrmlParserDefs.h" +#include "vrmlNodeType.h" +#include "vrmlNode.h" +#include "standard_nodes.h" +#include "zStream.h" +#include "virtualFileSystem.h" + +using std::istream; +using std::istringstream; +using std::string; + +extern int vrmlyyparse(); +extern void vrmlyyResetLineNumber(); +extern int vrmlyydebug; +extern int vrmlyy_flex_debug; + +extern VrmlScene *parsed_scene; + +/** + * Loads the set of standard VRML node definitions into the parser, if it has + * not already been loaded. + */ +static bool +get_standard_nodes() { + static bool got_standard_nodes = false; + static bool read_ok = true; + if (got_standard_nodes) { + return read_ok; + } + + // The standardNodes.wrl file has been compiled into this binary. Extract + // it out. + + string data((const char *)standard_nodes_data, standard_nodes_data_len); + +#ifdef HAVE_ZLIB + // The data is stored compressed; decompress it on-the-fly. + istringstream inz(data); + IDecompressStream in(&inz, false); + +#else + // The data is stored uncompressed, so just load it. + istringstream in(data); +#endif // HAVE_ZLIB + + vrml_init_parser(in, "standardNodes.wrl"); + if (vrmlyyparse() != 0) { + read_ok = false; + } + vrml_cleanup_parser(); + + got_standard_nodes = true; + return read_ok; +} + +/** + * Reads the named VRML file and returns a corresponding VrmlScene, or NULL if + * there is a parse error. + */ +VrmlScene * +parse_vrml(Filename filename) { + filename.set_text(); + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + istream *in = vfs->open_read_file(filename, true); + if (in == nullptr) { + nout << "Cannot open " << filename << " for reading.\n"; + return nullptr; + } + VrmlScene *result = parse_vrml(*in, filename); + vfs->close_read_file(in); + return result; +} + +/** + * Reads the indicated input stream and returns a corresponding VrmlScene, or + * NULL if there is a parse error. + */ +VrmlScene * +parse_vrml(istream &in, const string &filename) { + if (!get_standard_nodes()) { + std::cerr << "Internal error--unable to parse VRML.\n"; + return nullptr; + } + + VrmlScene *scene = nullptr; + VrmlNodeType::pushNameSpace(); + + vrml_init_parser(in, filename); + if (vrmlyyparse() == 0) { + scene = parsed_scene; + } + vrml_cleanup_parser(); + + VrmlNodeType::popNameSpace(); + + return scene; +} + +#if 0 +int +main(int argc, char *argv[]) { + if (argc < 2) { + std::cerr << "parse_vrml filename.wrl\n"; + exit(1); + } + + VrmlScene *scene = parse_vrml(argv[1]); + if (scene == nullptr) { + exit(1); + } + + std::cout << *scene << "\n"; + return (0); +} +#endif diff --git a/pandatool/src/vrml/parse_vrml.h b/pandatool/src/vrml/parse_vrml.h new file mode 100644 index 00000000..81a04847 --- /dev/null +++ b/pandatool/src/vrml/parse_vrml.h @@ -0,0 +1,23 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file parse_vrml.h + * @author drose + * @date 1999-06-24 + */ + +#ifndef PARSE_VRML_H +#define PARSE_VRML_H + +#include "vrmlNode.h" +#include "filename.h" + +VrmlScene *parse_vrml(Filename filename); +VrmlScene *parse_vrml(std::istream &in, const std::string &filename); + +#endif diff --git a/pandatool/src/vrml/standardNodes.wrl b/pandatool/src/vrml/standardNodes.wrl new file mode 100644 index 00000000..1764f41f --- /dev/null +++ b/pandatool/src/vrml/standardNodes.wrl @@ -0,0 +1,488 @@ +#VRML V2.0 utf8 +# +# ************************************************** +# * VRML 2.0 Parser +# * Copyright (C) 1996 Silicon Graphics, Inc. +# * +# * Author(s) : Gavin Bell +# * Daniel Woods (first port) +# ************************************************** +# +# Definitions for all of the nodes built-in to the spec. +# Taken almost directly from the VRML 2.0 final spec: + +PROTO Anchor [ + eventIn MFNode addChildren + eventIn MFNode removeChildren + exposedField MFNode children [] + exposedField SFString description "" + exposedField MFString parameter [] + exposedField MFString url [] + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 +] { } + +PROTO Appearance [ + exposedField SFNode material NULL + exposedField SFNode texture NULL + exposedField SFNode textureTransform NULL +] { } + +PROTO AudioClip [ + exposedField SFString description "" + exposedField SFBool loop FALSE + exposedField SFFloat pitch 1.0 + exposedField SFTime startTime 0 + exposedField SFTime stopTime 0 + exposedField MFString url [] + eventOut SFTime duration_changed + eventOut SFBool isActive +] { } + +PROTO Background [ + eventIn SFBool set_bind + exposedField MFFloat groundAngle [] + exposedField MFColor groundColor [] + exposedField MFString backUrl [] + exposedField MFString bottomUrl [] + exposedField MFString frontUrl [] + exposedField MFString leftUrl [] + exposedField MFString rightUrl [] + exposedField MFString topUrl [] + exposedField MFFloat skyAngle [] + exposedField MFColor skyColor [ 0 0 0 ] + eventOut SFBool isBound +] { } + +PROTO Billboard [ + eventIn MFNode addChildren + eventIn MFNode removeChildren + exposedField SFVec3f axisOfRotation 0 1 0 + exposedField MFNode children [] + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 +] { } + +PROTO Box [ + field SFVec3f size 2 2 2 +] { } + +PROTO Collision [ + eventIn MFNode addChildren + eventIn MFNode removeChildren + exposedField MFNode children [] + exposedField SFBool collide TRUE + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 + field SFNode proxy NULL + eventOut SFTime collideTime +] { } + +PROTO Color [ + exposedField MFColor color [] +] { } + +PROTO ColorInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFColor keyValue [] + eventOut SFColor value_changed +] { } + +PROTO Cone [ + field SFFloat bottomRadius 1 + field SFFloat height 2 + field SFBool side TRUE + field SFBool bottom TRUE +] { } + +PROTO Coordinate [ + exposedField MFVec3f point [] +] { } + +PROTO CoordinateInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFVec3f keyValue [] + eventOut MFVec3f value_changed +] { } + +PROTO Cylinder [ + field SFBool bottom TRUE + field SFFloat height 2 + field SFFloat radius 1 + field SFBool side TRUE + field SFBool top TRUE +] { } + +PROTO CylinderSensor [ + exposedField SFBool autoOffset TRUE + exposedField SFFloat diskAngle 0.262 + exposedField SFBool enabled TRUE + exposedField SFFloat maxAngle -1 + exposedField SFFloat minAngle 0 + exposedField SFFloat offset 0 + eventOut SFBool isActive + eventOut SFRotation rotation_changed + eventOut SFVec3f trackPoint_changed +] { } + +PROTO DirectionalLight [ + exposedField SFFloat ambientIntensity 0 + exposedField SFColor color 1 1 1 + exposedField SFVec3f direction 0 0 -1 + exposedField SFFloat intensity 1 + exposedField SFBool on TRUE +] { } + +PROTO ElevationGrid [ + eventIn MFFloat set_height + exposedField SFNode color NULL + exposedField SFNode normal NULL + exposedField SFNode texCoord NULL + field SFBool ccw TRUE + field SFBool colorPerVertex TRUE + field SFFloat creaseAngle 0 + field MFFloat height [] + field SFBool normalPerVertex TRUE + field SFBool solid TRUE + field SFInt32 xDimension 0 + field SFFloat xSpacing 0.0 + field SFInt32 zDimension 0 + field SFFloat zSpacing 0.0 + +] { } + +PROTO Extrusion [ + eventIn MFVec2f set_crossSection + eventIn MFRotation set_orientation + eventIn MFVec2f set_scale + eventIn MFVec3f set_spine + field SFBool beginCap TRUE + field SFBool ccw TRUE + field SFBool convex TRUE + field SFFloat creaseAngle 0 + field MFVec2f crossSection [ 1 1, 1 -1, -1 -1, -1 1, 1 1 ] + field SFBool endCap TRUE + field MFRotation orientation 0 0 1 0 + field MFVec2f scale 1 1 + field SFBool solid TRUE + field MFVec3f spine [ 0 0 0, 0 1 0 ] +] { } + +PROTO Fog [ + exposedField SFColor color 1 1 1 + exposedField SFString fogType "LINEAR" + exposedField SFFloat visibilityRange 0 + eventIn SFBool set_bind + eventOut SFBool isBound +] { } + +PROTO FontStyle [ + field SFString family "SERIF" + field SFBool horizontal TRUE + field MFString justify "BEGIN" + field SFString language "" + field SFBool leftToRight TRUE + field SFFloat size 1.0 + field SFFloat spacing 1.0 + field SFString style "PLAIN" + field SFBool topToBottom TRUE +] { } + +PROTO Group [ + eventIn MFNode addChildren + eventIn MFNode removeChildren + exposedField MFNode children [] + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 +] { } + +PROTO ImageTexture [ + exposedField MFString url [] + field SFBool repeatS TRUE + field SFBool repeatT TRUE +] { } + +PROTO IndexedFaceSet [ + eventIn MFInt32 set_colorIndex + eventIn MFInt32 set_coordIndex + eventIn MFInt32 set_normalIndex + eventIn MFInt32 set_texCoordIndex + exposedField SFNode color NULL + exposedField SFNode coord NULL + exposedField SFNode normal NULL + exposedField SFNode texCoord NULL + field SFBool ccw TRUE + field MFInt32 colorIndex [] + field SFBool colorPerVertex TRUE + field SFBool convex TRUE + field MFInt32 coordIndex [] + field SFFloat creaseAngle 0 + field MFInt32 normalIndex [] + field SFBool normalPerVertex TRUE + field SFBool solid TRUE + field MFInt32 texCoordIndex [] +] { } + +PROTO IndexedLineSet [ + eventIn MFInt32 set_colorIndex + eventIn MFInt32 set_coordIndex + exposedField SFNode color NULL + exposedField SFNode coord NULL + field MFInt32 colorIndex [] + field SFBool colorPerVertex TRUE + field MFInt32 coordIndex [] +] { } + +PROTO Inline [ + exposedField MFString url [] + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 +] { } + +PROTO LOD [ + exposedField MFNode level [] + field SFVec3f center 0 0 0 + field MFFloat range [] +] { } + +PROTO Material [ + exposedField SFFloat ambientIntensity 0.2 + exposedField SFColor diffuseColor 0.8 0.8 0.8 + exposedField SFColor emissiveColor 0 0 0 + exposedField SFFloat shininess 0.2 + exposedField SFColor specularColor 0 0 0 + exposedField SFFloat transparency 0 +] { } + +PROTO MovieTexture [ + exposedField SFBool loop FALSE + exposedField SFFloat speed 1 + exposedField SFTime startTime 0 + exposedField SFTime stopTime 0 + exposedField MFString url [] + field SFBool repeatS TRUE + field SFBool repeatT TRUE + eventOut SFFloat duration_changed + eventOut SFBool isActive +] { } + +PROTO NavigationInfo [ + eventIn SFBool set_bind + exposedField MFFloat avatarSize [ 0.25, 1.6, 0.75 ] + exposedField SFBool headlight TRUE + exposedField SFFloat speed 1.0 + exposedField MFString type "WALK" + exposedField SFFloat visibilityLimit 0.0 + eventOut SFBool isBound +] { } + +PROTO Normal [ + exposedField MFVec3f vector [] +] { } + +PROTO NormalInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFVec3f keyValue [] + eventOut MFVec3f value_changed +] { } + +PROTO OrientationInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFRotation keyValue [] + eventOut SFRotation value_changed +] { } + +PROTO PixelTexture [ + exposedField SFImage image 0 0 0 + field SFBool repeatS TRUE + field SFBool repeatT TRUE +] { } + +PROTO PlaneSensor [ + exposedField SFBool autoOffset TRUE + exposedField SFBool enabled TRUE + exposedField SFVec2f maxPosition -1 -1 + exposedField SFVec2f minPosition 0 0 + exposedField SFVec3f offset 0 0 0 + eventOut SFBool isActive + eventOut SFVec3f trackPoint_changed + eventOut SFVec3f translation_changed +] { } + +PROTO PointLight [ + exposedField SFFloat ambientIntensity 0 + exposedField SFVec3f attenuation 1 0 0 + exposedField SFColor color 1 1 1 + exposedField SFFloat intensity 1 + exposedField SFVec3f location 0 0 0 + exposedField SFBool on TRUE + exposedField SFFloat radius 100 +] { } + +PROTO PointSet [ + exposedField SFNode color NULL + exposedField SFNode coord NULL +] { } + +PROTO PositionInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFVec3f keyValue [] + eventOut SFVec3f value_changed +] { } + +PROTO ProximitySensor [ + exposedField SFVec3f center 0 0 0 + exposedField SFVec3f size 0 0 0 + exposedField SFBool enabled TRUE + eventOut SFBool isActive + eventOut SFVec3f position_changed + eventOut SFRotation orientation_changed + eventOut SFTime enterTime + eventOut SFTime exitTime +] { } + +PROTO ScalarInterpolator [ + eventIn SFFloat set_fraction + exposedField MFFloat key [] + exposedField MFFloat keyValue [] + eventOut SFFloat value_changed +] { } + +PROTO Script [ + exposedField MFString url [ ] + field SFBool directOutput FALSE + field SFBool mustEvaluate FALSE +] { } + +PROTO Shape [ + field SFNode appearance NULL + field SFNode geometry NULL +] { } + +PROTO Sound [ + exposedField SFVec3f direction 0 0 1 + exposedField SFFloat intensity 1 + exposedField SFVec3f location 0 0 0 + exposedField SFFloat maxBack 10 + exposedField SFFloat maxFront 10 + exposedField SFFloat minBack 1 + exposedField SFFloat minFront 1 + exposedField SFFloat priority 0 + exposedField SFNode source NULL + field SFBool spatialize TRUE +] { } + +PROTO Sphere [ + field SFFloat radius 1 +] { } + +PROTO SphereSensor [ + exposedField SFBool autoOffset TRUE + exposedField SFBool enabled TRUE + exposedField SFRotation offset 0 1 0 0 + eventOut SFBool isActive + eventOut SFRotation rotation_changed + eventOut SFVec3f trackPoint_changed +] { } + +PROTO SpotLight [ + exposedField SFFloat ambientIntensity 0 + exposedField SFVec3f attenuation 1 0 0 + exposedField SFFloat beamWidth 1.570796 + exposedField SFColor color 1 1 1 + exposedField SFFloat cutOffAngle 0.785398 + exposedField SFVec3f direction 0 0 -1 + exposedField SFFloat intensity 1 + exposedField SFVec3f location 0 0 0 + exposedField SFBool on TRUE + exposedField SFFloat radius 100 +] { } + +PROTO Switch [ + exposedField MFNode choice [] + exposedField SFInt32 whichChild -1 +] { } + +PROTO Text [ + exposedField MFString string [] + field SFNode fontStyle NULL + field MFFloat length [] + field SFFloat maxExtent 0.0 +] { } + +PROTO TextureCoordinate [ + exposedField MFVec2f point [] +] { } + +PROTO TextureTransform [ + exposedField SFVec2f center 0 0 + exposedField SFFloat rotation 0 + exposedField SFVec2f scale 1 1 + exposedField SFVec2f translation 0 0 +] { } + +PROTO TimeSensor [ + exposedField SFTime cycleInterval 1 + exposedField SFBool enabled TRUE + exposedField SFBool loop FALSE + exposedField SFTime startTime 0 + exposedField SFTime stopTime 0 + eventOut SFTime cycleTime + eventOut SFFloat fraction_changed + eventOut SFBool isActive + eventOut SFTime time +] { } + +PROTO TouchSensor [ + exposedField SFBool enabled TRUE + eventOut SFVec3f hitNormal_changed + eventOut SFVec3f hitPoint_changed + eventOut SFVec2f hitTexCoord_changed + eventOut SFBool isActive + eventOut SFBool isOver + eventOut SFTime touchTime +] { } + +PROTO Transform [ + eventIn MFNode addChildren + eventIn MFNode removeChildren + exposedField SFVec3f center 0 0 0 + exposedField MFNode children [] + exposedField SFRotation rotation 0 0 1 0 + exposedField SFVec3f scale 1 1 1 + exposedField SFRotation scaleOrientation 0 0 1 0 + exposedField SFVec3f translation 0 0 0 + field SFVec3f bboxCenter 0 0 0 + field SFVec3f bboxSize -1 -1 -1 +] { } + +PROTO Viewpoint [ + eventIn SFBool set_bind + exposedField SFFloat fieldOfView 0.785398 + exposedField SFBool jump TRUE + exposedField SFRotation orientation 0 0 1 0 + exposedField SFVec3f position 0 0 10 + field SFString description "" + eventOut SFTime bindTime + eventOut SFBool isBound +] { } + +PROTO VisibilitySensor [ + exposedField SFVec3f center 0 0 0 + exposedField SFBool enabled TRUE + exposedField SFVec3f size 0 0 0 + eventOut SFTime enterTime + eventOut SFTime exitTime + eventOut SFBool isActive +] { } + +PROTO WorldInfo [ + field MFString info [] + field SFString title "" +] { } + diff --git a/pandatool/src/vrml/standardNodes.wrl.c b/pandatool/src/vrml/standardNodes.wrl.c new file mode 100644 index 00000000..bef39563 --- /dev/null +++ b/pandatool/src/vrml/standardNodes.wrl.c @@ -0,0 +1,1342 @@ + +/* + * This table was generated by the command: + * + * bin2c -n standard_nodes_data -o standardNodes.wrl.c standardNodes.wrl + */ + +#include + +const unsigned char standard_nodes_data[] = { + 0x23, 0x56, 0x52, 0x4d, 0x4c, 0x20, 0x56, 0x32, 0x2e, 0x30, 0x20, + 0x75, 0x74, 0x66, 0x38, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x0a, 0x23, 0x20, 0x2a, 0x20, 0x56, 0x52, + 0x4d, 0x4c, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x72, 0x0a, 0x23, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x31, + 0x39, 0x39, 0x36, 0x20, 0x53, 0x69, 0x6c, 0x69, 0x63, 0x6f, 0x6e, + 0x20, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2c, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x23, 0x20, 0x2a, 0x0a, 0x23, 0x20, + 0x2a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x28, 0x73, 0x29, + 0x20, 0x20, 0x20, 0x20, 0x3a, 0x20, 0x47, 0x61, 0x76, 0x69, 0x6e, + 0x20, 0x42, 0x65, 0x6c, 0x6c, 0x0a, 0x23, 0x20, 0x2a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x44, 0x61, 0x6e, 0x69, 0x65, 0x6c, 0x20, 0x57, + 0x6f, 0x6f, 0x64, 0x73, 0x20, 0x28, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x20, 0x70, 0x6f, 0x72, 0x74, 0x29, 0x0a, 0x23, 0x20, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x44, 0x65, + 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x62, 0x75, + 0x69, 0x6c, 0x74, 0x2d, 0x69, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x0a, 0x23, 0x20, + 0x54, 0x61, 0x6b, 0x65, 0x6e, 0x20, 0x61, 0x6c, 0x6d, 0x6f, 0x73, + 0x74, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x56, 0x52, + 0x4d, 0x4c, 0x20, 0x32, 0x2e, 0x30, 0x20, 0x66, 0x69, 0x6e, 0x61, + 0x6c, 0x20, 0x73, 0x70, 0x65, 0x63, 0x3a, 0x0a, 0x0a, 0x50, 0x52, + 0x4f, 0x54, 0x4f, 0x20, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, + 0x65, 0x20, 0x20, 0x20, 0x61, 0x64, 0x64, 0x43, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, + 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, + 0x20, 0x20, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x22, 0x20, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x20, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, + 0x75, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x62, + 0x62, 0x6f, 0x78, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x20, 0x62, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, + 0x20, 0x2d, 0x31, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x41, 0x70, 0x70, 0x65, 0x61, + 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x61, 0x74, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x43, 0x6c, 0x69, 0x70, + 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x22, 0x22, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, + 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x41, 0x4c, 0x53, + 0x45, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x70, 0x69, 0x74, 0x63, 0x68, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x2e, 0x30, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, + 0x6d, 0x65, 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, + 0x20, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, + 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x75, + 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, + 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x69, 0x73, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x42, 0x61, 0x63, 0x6b, + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, + 0x73, 0x65, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x41, 0x6e, 0x67, 0x6c, 0x65, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x5b, 0x5d, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x55, 0x72, 0x6c, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x62, + 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x55, 0x72, 0x6c, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x55, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x55, 0x72, 0x6c, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x55, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x70, 0x55, 0x72, 0x6c, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x73, 0x6b, 0x79, 0x41, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, + 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x73, 0x6b, 0x79, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, + 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x5d, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x69, 0x73, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x42, 0x69, 0x6c, 0x6c, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x20, 0x61, 0x64, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, + 0x72, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, + 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x20, 0x61, 0x78, 0x69, 0x73, 0x4f, 0x66, 0x52, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x43, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, + 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, + 0x33, 0x66, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, + 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x31, + 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x42, 0x6f, + 0x78, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x32, 0x20, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, + 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x5b, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x61, 0x64, + 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, + 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x69, + 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, + 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x62, 0x62, + 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, + 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, + 0x4c, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, + 0x65, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, + 0x61, 0x74, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x74, + 0x5f, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, + 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x43, 0x6f, 0x6e, 0x65, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x74, 0x74, + 0x6f, 0x6d, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x20, 0x31, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x32, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, + 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x64, 0x65, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, + 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, + 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, + 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x66, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6b, 0x65, + 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, + 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, + 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x56, 0x65, + 0x63, 0x33, 0x66, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, 0x79, + 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x74, + 0x74, 0x6f, 0x6d, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x20, 0x20, 0x32, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, 0x72, 0x61, 0x64, 0x69, + 0x75, 0x73, 0x20, 0x20, 0x31, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x64, 0x65, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, + 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x5d, 0x20, 0x7b, + 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x43, + 0x79, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x72, 0x53, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x75, + 0x74, 0x6f, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x6b, + 0x41, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x20, 0x30, 0x2e, 0x32, 0x36, + 0x32, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x41, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x20, 0x20, 0x2d, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x6d, 0x69, 0x6e, 0x41, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x20, 0x20, + 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, + 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x20, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61, + 0x6d, 0x62, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x74, 0x79, 0x20, 0x20, 0x30, 0x20, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, + 0x66, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, + 0x30, 0x20, 0x2d, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x74, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x31, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x6f, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x20, 0x0a, 0x5d, 0x20, 0x7b, + 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x45, + 0x6c, 0x65, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x72, 0x69, + 0x64, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, + 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, + 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x63, 0x63, 0x77, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x41, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, + 0x20, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x50, 0x65, 0x72, + 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x20, 0x20, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, + 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x20, 0x78, 0x44, 0x69, + 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x78, 0x53, 0x70, + 0x61, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x2e, 0x30, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x20, 0x7a, + 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x7a, + 0x53, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x2e, 0x30, 0x0a, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x45, 0x78, 0x74, 0x72, 0x75, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, + 0x6e, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x20, + 0x20, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, + 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x4d, 0x46, 0x52, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x74, 0x5f, + 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, + 0x4d, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x20, 0x20, 0x20, + 0x73, 0x65, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x4d, 0x46, + 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, 0x73, 0x65, + 0x74, 0x5f, 0x73, 0x70, 0x69, 0x6e, 0x65, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, + 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x65, 0x67, 0x69, + 0x6e, 0x43, 0x61, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x63, 0x77, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x78, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x41, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, + 0x4d, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x20, 0x20, 0x20, + 0x63, 0x72, 0x6f, 0x73, 0x73, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x20, 0x31, 0x20, 0x31, + 0x2c, 0x20, 0x31, 0x20, 0x2d, 0x31, 0x2c, 0x20, 0x2d, 0x31, 0x20, + 0x2d, 0x31, 0x2c, 0x20, 0x2d, 0x31, 0x20, 0x31, 0x2c, 0x20, 0x31, + 0x20, 0x31, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x43, 0x61, 0x70, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, + 0x30, 0x20, 0x31, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x32, + 0x66, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x31, 0x20, 0x31, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x20, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, + 0x20, 0x20, 0x73, 0x70, 0x69, 0x6e, 0x65, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x20, 0x30, + 0x20, 0x30, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, + 0x20, 0x5d, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, + 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x46, 0x6f, 0x67, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, + 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x67, 0x54, 0x79, 0x70, + 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x4c, 0x49, 0x4e, 0x45, 0x41, 0x52, 0x22, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, + 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x73, + 0x65, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x69, + 0x73, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x46, 0x6f, + 0x6e, 0x74, 0x53, 0x74, 0x79, 0x6c, 0x65, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x45, 0x52, 0x49, 0x46, + 0x22, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x20, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6a, 0x75, + 0x73, 0x74, 0x69, 0x66, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x42, 0x45, 0x47, 0x49, 0x4e, 0x22, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x6c, 0x65, 0x66, 0x74, 0x54, 0x6f, 0x52, 0x69, 0x67, 0x68, + 0x74, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x31, 0x2e, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x31, 0x2e, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x22, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, + 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70, 0x54, 0x6f, 0x42, + 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, + 0x61, 0x64, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, + 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x63, 0x68, 0x69, 0x6c, + 0x64, 0x72, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x62, 0x62, 0x6f, 0x78, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x62, 0x62, 0x6f, 0x78, + 0x53, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x2d, 0x31, 0x20, 0x2d, + 0x31, 0x20, 0x2d, 0x31, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x75, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, + 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x53, + 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, + 0x65, 0x61, 0x74, 0x54, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x5d, + 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x20, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x46, 0x61, 0x63, + 0x65, 0x53, 0x65, 0x74, 0x20, 0x5b, 0x20, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x73, + 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, + 0x6e, 0x74, 0x33, 0x32, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, + 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, + 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, + 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x73, 0x65, 0x74, 0x5f, + 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, + 0x6f, 0x64, 0x65, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, + 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x63, 0x6f, 0x6f, + 0x72, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, + 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, + 0x65, 0x20, 0x20, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, + 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x63, 0x63, 0x77, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, + 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x78, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, + 0x6e, 0x74, 0x33, 0x32, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x72, 0x65, 0x61, 0x73, + 0x65, 0x41, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, + 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x6e, 0x6f, 0x72, 0x6d, + 0x61, 0x6c, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x73, + 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, + 0x32, 0x20, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, + 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x20, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x4c, + 0x69, 0x6e, 0x65, 0x53, 0x65, 0x74, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, + 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, + 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x63, + 0x6f, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, + 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, + 0x4c, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, + 0x64, 0x65, 0x20, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, + 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, + 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x5d, + 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x20, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x20, 0x75, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, + 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x43, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, + 0x66, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, + 0x20, 0x20, 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, + 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x20, 0x4c, 0x4f, 0x44, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x20, + 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, + 0x66, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x20, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x61, 0x6d, 0x62, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x20, 0x20, 0x30, 0x2e, 0x32, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x20, 0x64, 0x69, 0x66, 0x66, 0x75, 0x73, 0x65, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x2e, + 0x38, 0x20, 0x30, 0x2e, 0x38, 0x20, 0x30, 0x2e, 0x38, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x65, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, + 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x69, 0x6e, 0x65, 0x73, + 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, + 0x2e, 0x32, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x70, 0x65, 0x63, 0x75, 0x6c, 0x61, + 0x72, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x30, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x4d, 0x6f, 0x76, 0x69, + 0x65, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, + 0x20, 0x20, 0x6c, 0x6f, 0x6f, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x46, 0x41, 0x4c, 0x53, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x73, + 0x70, 0x65, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, + 0x20, 0x20, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x73, 0x74, 0x6f, 0x70, + 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, + 0x75, 0x72, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, + 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x53, + 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, + 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x54, 0x20, 0x20, 0x20, 0x20, + 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, + 0x6c, 0x20, 0x20, 0x20, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, + 0x4f, 0x54, 0x4f, 0x20, 0x4e, 0x61, 0x76, 0x69, 0x67, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x73, 0x65, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x64, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x53, 0x69, 0x7a, 0x65, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x20, 0x30, 0x2e, + 0x32, 0x35, 0x2c, 0x20, 0x31, 0x2e, 0x36, 0x2c, 0x20, 0x30, 0x2e, + 0x37, 0x35, 0x20, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x68, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x73, 0x70, + 0x65, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x41, 0x4c, 0x4b, + 0x22, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x20, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x20, 0x20, + 0x30, 0x2e, 0x30, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x69, 0x73, 0x42, 0x6f, + 0x75, 0x6e, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x4e, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, + 0x65, 0x63, 0x33, 0x66, 0x20, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x20, 0x5b, 0x5d, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x4e, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, + 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x66, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6b, 0x65, + 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, + 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, + 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x56, 0x65, + 0x63, 0x33, 0x66, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x4f, 0x72, + 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x66, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x6b, 0x65, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x52, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x50, 0x69, 0x78, 0x65, + 0x6c, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x20, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x72, + 0x65, 0x70, 0x65, 0x61, 0x74, 0x53, 0x20, 0x20, 0x20, 0x20, 0x54, + 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, + 0x74, 0x54, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x53, 0x65, 0x6e, 0x73, + 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x4f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, + 0x20, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x6d, 0x61, + 0x78, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, + 0x31, 0x20, 0x2d, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x6d, 0x69, 0x6e, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x30, 0x20, 0x30, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, + 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x69, 0x73, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x74, 0x72, 0x61, + 0x63, 0x6b, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x4c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61, 0x6d, + 0x62, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x74, 0x79, 0x20, 0x20, 0x30, 0x20, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x61, 0x74, + 0x74, 0x65, 0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, + 0x20, 0x31, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x74, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, + 0x63, 0x33, 0x66, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x6f, 0x6e, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x20, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, + 0x61, 0x64, 0x69, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x30, 0x30, 0x0a, 0x5d, + 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x20, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x65, 0x74, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, + 0x65, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x63, + 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4e, + 0x55, 0x4c, 0x4c, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, + 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x50, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, + 0x61, 0x74, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x74, + 0x5f, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, + 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, + 0x33, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x50, 0x72, 0x6f, 0x78, 0x69, 0x6d, 0x69, 0x74, 0x79, 0x53, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, + 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, + 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, + 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, + 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x53, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x78, 0x69, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, + 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x61, 0x72, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x6f, 0x6c, 0x61, 0x74, 0x6f, + 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x66, 0x72, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6b, 0x65, 0x79, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, 0x5b, + 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, + 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, + 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x75, 0x72, 0x6c, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5b, 0x20, 0x5d, 0x20, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x20, 0x46, + 0x41, 0x4c, 0x53, 0x45, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x20, 0x20, 0x46, + 0x41, 0x4c, 0x53, 0x45, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x53, 0x68, 0x61, 0x70, + 0x65, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x61, 0x70, 0x70, + 0x65, 0x61, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x4e, 0x55, 0x4c, + 0x4c, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x67, 0x65, 0x6f, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, + 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x6d, 0x61, 0x78, + 0x42, 0x61, 0x63, 0x6b, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x31, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x6f, + 0x6e, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x30, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x6d, 0x69, + 0x6e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x20, 0x20, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, 0x20, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, + 0x73, 0x70, 0x61, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x5d, 0x20, 0x7b, + 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x53, + 0x70, 0x68, 0x65, 0x72, 0x65, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x20, 0x20, 0x31, + 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x20, 0x53, 0x70, 0x68, 0x65, 0x72, 0x65, 0x53, 0x65, + 0x6e, 0x73, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x61, 0x75, 0x74, 0x6f, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, + 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, + 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x52, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, + 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x73, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, + 0x61, 0x63, 0x6b, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, + 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x53, 0x70, 0x6f, + 0x74, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61, + 0x6d, 0x62, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x74, 0x79, 0x20, 0x20, 0x30, 0x20, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x61, + 0x74, 0x74, 0x65, 0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x62, 0x65, 0x61, 0x6d, 0x57, 0x69, 0x64, 0x74, 0x68, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x2e, + 0x35, 0x37, 0x30, 0x37, 0x39, 0x36, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x63, 0x75, 0x74, 0x4f, 0x66, 0x66, 0x41, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x2e, 0x37, + 0x38, 0x35, 0x33, 0x39, 0x38, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x64, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x2d, 0x31, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, + 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, + 0x66, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x6f, 0x6e, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, 0x61, + 0x64, 0x69, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x30, 0x30, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x20, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, + 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x77, 0x68, + 0x69, 0x63, 0x68, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x2d, 0x31, + 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x20, 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x20, + 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, + 0x53, 0x74, 0x79, 0x6c, 0x65, 0x20, 0x4e, 0x55, 0x4c, 0x4c, 0x0a, + 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x46, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x20, 0x20, + 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x20, 0x6d, 0x61, 0x78, + 0x45, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x30, 0x2e, 0x30, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x20, 0x5b, 0x0a, + 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, + 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x5b, 0x5d, 0x0a, 0x5d, + 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, + 0x20, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x5b, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x63, + 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, + 0x20, 0x31, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, + 0x63, 0x32, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x5d, 0x20, + 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, + 0x65, 0x20, 0x20, 0x20, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x20, 0x31, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x6c, + 0x6f, 0x6f, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x46, 0x41, 0x4c, 0x53, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x54, + 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x54, + 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x0a, + 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, + 0x20, 0x20, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x20, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, + 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, 0x20, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, + 0x74, 0x69, 0x6d, 0x65, 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, + 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x54, 0x6f, 0x75, 0x63, + 0x68, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x54, 0x52, 0x55, + 0x45, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, + 0x33, 0x66, 0x20, 0x68, 0x69, 0x74, 0x4e, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x68, 0x69, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, + 0x46, 0x56, 0x65, 0x63, 0x32, 0x66, 0x20, 0x68, 0x69, 0x74, 0x54, + 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, + 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x69, 0x73, 0x41, 0x63, 0x74, + 0x69, 0x76, 0x65, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x69, 0x73, 0x4f, 0x76, 0x65, 0x72, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, + 0x20, 0x20, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x54, 0x69, 0x6d, 0x65, + 0x0a, 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x20, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, + 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x64, + 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x4d, 0x46, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, + 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, + 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x4d, 0x46, 0x4e, 0x6f, + 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, + 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, + 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, + 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, + 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, + 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x63, + 0x61, 0x6c, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x4f, 0x72, + 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x30, + 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, + 0x20, 0x30, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, + 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x62, 0x6f, + 0x78, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x62, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x31, + 0x20, 0x2d, 0x31, 0x20, 0x2d, 0x31, 0x0a, 0x5d, 0x20, 0x7b, 0x20, + 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x20, 0x56, 0x69, + 0x65, 0x77, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x64, + 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x46, 0x6c, 0x6f, 0x61, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4f, + 0x66, 0x56, 0x69, 0x65, 0x77, 0x20, 0x20, 0x20, 0x20, 0x30, 0x2e, + 0x37, 0x38, 0x35, 0x33, 0x39, 0x38, 0x0a, 0x20, 0x20, 0x65, 0x78, + 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6a, 0x75, 0x6d, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, + 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x53, 0x46, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, + 0x20, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, + 0x65, 0x63, 0x33, 0x66, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x30, 0x0a, 0x20, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x20, + 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x20, 0x20, 0x20, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, 0x65, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x62, 0x69, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x0a, 0x20, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x73, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x20, 0x5b, 0x0a, 0x20, + 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, + 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x20, 0x20, 0x30, 0x20, + 0x30, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x65, 0x78, 0x70, 0x6f, 0x73, + 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x42, + 0x6f, 0x6f, 0x6c, 0x20, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x20, 0x20, 0x54, 0x52, 0x55, 0x45, 0x0a, 0x20, 0x20, 0x65, + 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x53, 0x46, 0x56, 0x65, 0x63, 0x33, 0x66, 0x20, 0x73, 0x69, + 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, + 0x30, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, + 0x65, 0x20, 0x20, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x54, 0x69, 0x6d, + 0x65, 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, + 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x54, 0x69, 0x6d, + 0x65, 0x20, 0x20, 0x65, 0x78, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, + 0x0a, 0x20, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4f, 0x75, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x53, 0x46, 0x42, 0x6f, 0x6f, 0x6c, + 0x20, 0x20, 0x69, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a, 0x50, 0x52, 0x4f, 0x54, + 0x4f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, + 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x4d, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, + 0x66, 0x6f, 0x20, 0x20, 0x5b, 0x5d, 0x0a, 0x20, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x53, 0x46, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x22, 0x22, 0x0a, + 0x5d, 0x20, 0x7b, 0x20, 0x7d, 0x0a, 0x0a +}; + +const int standard_nodes_data_len = 14604; + diff --git a/pandatool/src/vrml/standardNodes.wrl.pz.c b/pandatool/src/vrml/standardNodes.wrl.pz.c new file mode 100644 index 00000000..f573f1c4 --- /dev/null +++ b/pandatool/src/vrml/standardNodes.wrl.pz.c @@ -0,0 +1,273 @@ + +/* + * This table was generated by the command: + * + * bin2c -n standard_nodes_data -o standardNodes.wrl.pz.c standardNodes.wrl.pz + */ + +#include + +const unsigned char standard_nodes_data[] = { + 0x78, 0x9c, 0xd5, 0x5b, 0x5b, 0x6f, 0xdb, 0x38, 0x16, 0x7e, 0xcf, + 0xaf, 0x20, 0xd2, 0x97, 0x76, 0xd1, 0x09, 0x62, 0x17, 0xbd, 0xcd, + 0x5b, 0x92, 0x26, 0x45, 0xb0, 0x69, 0x12, 0x44, 0x6e, 0xfa, 0x50, + 0x14, 0x03, 0x5a, 0xa2, 0x6d, 0x4e, 0x64, 0x51, 0xa0, 0x28, 0xd7, + 0xee, 0x62, 0xff, 0xfb, 0xf2, 0x2e, 0x4a, 0xbc, 0x58, 0xdd, 0x66, + 0x3a, 0x1d, 0xa5, 0x45, 0xd3, 0xf8, 0xd3, 0xe1, 0xe1, 0x39, 0x87, + 0xe7, 0xca, 0x3c, 0xb9, 0xbf, 0xfb, 0x70, 0x05, 0xee, 0xa7, 0x47, + 0xc7, 0xa0, 0x65, 0x8b, 0x37, 0x07, 0x4f, 0x0e, 0x9e, 0x80, 0x7f, + 0x7d, 0xf7, 0x23, 0x5e, 0x02, 0x92, 0x92, 0x20, 0x74, 0x0b, 0x69, + 0x83, 0xa8, 0xfc, 0xd9, 0x19, 0xa9, 0x77, 0x14, 0x2f, 0x57, 0x0c, + 0x3c, 0x3d, 0x7b, 0x06, 0x26, 0x6f, 0xdf, 0xbe, 0x02, 0x19, 0x2e, + 0x71, 0x4e, 0x2a, 0xf0, 0x9e, 0xc2, 0x7a, 0x85, 0xf3, 0xe6, 0x39, + 0xb8, 0xac, 0xf2, 0x23, 0x81, 0x96, 0x6f, 0x9c, 0xb4, 0x6c, 0x45, + 0xe8, 0xd3, 0xe6, 0x19, 0xe0, 0xcf, 0xef, 0xe0, 0x3d, 0xdc, 0xe0, + 0x0a, 0x9c, 0xa2, 0xb2, 0x94, 0x9f, 0x0e, 0x9e, 0x77, 0xb0, 0xc2, + 0xa8, 0x04, 0x9f, 0x08, 0x29, 0x1a, 0xf0, 0x74, 0x81, 0x69, 0xc3, + 0x40, 0x4d, 0x28, 0x7b, 0xf6, 0x7f, 0xee, 0x82, 0xbf, 0xf6, 0x0e, + 0x2d, 0x70, 0x85, 0x19, 0x26, 0x55, 0x03, 0x16, 0x84, 0x02, 0x58, + 0x96, 0x80, 0x2c, 0x00, 0x5b, 0x21, 0x50, 0x91, 0x02, 0x35, 0x60, + 0xde, 0xe2, 0x92, 0xfd, 0xc6, 0x99, 0x62, 0x44, 0xfe, 0xb4, 0xa9, + 0x91, 0x64, 0x7f, 0x06, 0x1f, 0x50, 0xc5, 0xe1, 0x6b, 0xc2, 0x99, + 0x28, 0x30, 0x45, 0x39, 0x2b, 0x77, 0x60, 0x41, 0xc9, 0x5a, 0xc2, + 0xac, 0x74, 0x38, 0x79, 0x58, 0xca, 0xb7, 0x7e, 0x3f, 0x38, 0xb8, + 0xbd, 0xbb, 0x99, 0xdd, 0x80, 0x93, 0x2a, 0xe7, 0x7b, 0x06, 0x9f, + 0x0f, 0x00, 0x40, 0x1b, 0x54, 0xb1, 0xcb, 0x4a, 0x6d, 0xef, 0xc3, + 0xc5, 0x35, 0x5f, 0x92, 0x7f, 0x03, 0x8b, 0xe2, 0x6c, 0x85, 0xcb, + 0x82, 0xa2, 0x2a, 0x8a, 0xa1, 0x68, 0x4d, 0x36, 0xc8, 0x85, 0x6d, + 0x6b, 0xd2, 0xa0, 0xe2, 0x82, 0x4b, 0xa8, 0xe8, 0x60, 0xb9, 0x06, + 0x18, 0x11, 0x7e, 0xfe, 0x32, 0x84, 0x66, 0x17, 0x19, 0xa3, 0xb8, + 0x5a, 0x02, 0xbe, 0xdb, 0x9c, 0xe2, 0x5a, 0xc8, 0x42, 0x42, 0x0f, + 0x0f, 0x81, 0x4f, 0x56, 0x63, 0x6b, 0x48, 0xe1, 0x1a, 0x31, 0x44, + 0xa3, 0x64, 0x2d, 0xb4, 0xa5, 0x65, 0x4f, 0x89, 0x12, 0xba, 0x90, + 0x18, 0xfd, 0x64, 0x17, 0xf7, 0x28, 0x7f, 0xb1, 0x00, 0x60, 0x3e, + 0x27, 0xdb, 0x33, 0xbe, 0x59, 0x43, 0xf6, 0x58, 0x7c, 0x25, 0xd1, + 0x19, 0xfe, 0x86, 0xcc, 0x07, 0xbf, 0x4d, 0xd4, 0x9f, 0x83, 0x2f, + 0xe0, 0x3f, 0xe0, 0xbf, 0x56, 0xd8, 0x75, 0x8d, 0x38, 0xb3, 0x55, + 0x8e, 0x94, 0xc0, 0xfb, 0x5b, 0x97, 0x52, 0x5a, 0x43, 0xbe, 0x22, + 0x86, 0x0e, 0x9b, 0xd7, 0x1f, 0xaf, 0xae, 0x22, 0x60, 0x86, 0xb6, + 0xac, 0xa5, 0x08, 0x7c, 0x0f, 0x78, 0xc6, 0xd7, 0x6f, 0xb8, 0x75, + 0xad, 0x35, 0xb8, 0xcf, 0x60, 0x5b, 0x60, 0x72, 0x56, 0xe2, 0xda, + 0xe7, 0x0f, 0x44, 0x94, 0x73, 0x78, 0x18, 0x42, 0x9e, 0x12, 0x22, + 0xb6, 0x50, 0x12, 0x52, 0x5b, 0xde, 0x2e, 0x4e, 0xae, 0xb2, 0xf3, + 0x10, 0xf8, 0xa2, 0x24, 0x90, 0x01, 0x50, 0x63, 0x96, 0xaf, 0x0c, + 0x78, 0x72, 0x74, 0x1c, 0x82, 0xce, 0xf0, 0x5a, 0x6c, 0xb7, 0x61, + 0x90, 0x32, 0xfd, 0x3d, 0x48, 0x03, 0x49, 0x6d, 0x70, 0x21, 0x60, + 0xd8, 0x32, 0x94, 0x05, 0x09, 0x53, 0xbf, 0x69, 0x99, 0x55, 0xb5, + 0xa6, 0x53, 0xb4, 0x14, 0x8a, 0xad, 0xff, 0x91, 0xaf, 0x60, 0xb5, + 0x44, 0x45, 0x08, 0xaa, 0xb7, 0x8f, 0x9b, 0x93, 0x9c, 0xe1, 0x0d, + 0xea, 0x0b, 0xf9, 0x14, 0xe6, 0x0f, 0x4b, 0x4a, 0xda, 0xaa, 0xf0, + 0x8f, 0x9d, 0x7d, 0xb5, 0x41, 0xec, 0x8f, 0x39, 0xae, 0x0a, 0xdf, + 0x94, 0xb5, 0xb4, 0x14, 0x85, 0x93, 0x6a, 0x59, 0xa2, 0xb0, 0xc9, + 0x9f, 0x91, 0x92, 0x1f, 0x6c, 0x8d, 0xd3, 0xff, 0x49, 0x1c, 0x8d, + 0x39, 0xe7, 0xea, 0xa3, 0x11, 0x42, 0x0a, 0x47, 0x18, 0x23, 0x6b, + 0x8d, 0x4c, 0xe0, 0xb8, 0xf7, 0xa9, 0x98, 0x21, 0x98, 0xc0, 0x95, + 0x68, 0xc1, 0xc6, 0xac, 0x2b, 0x5d, 0xf9, 0x08, 0x7a, 0x5c, 0xe1, + 0x96, 0x5c, 0x10, 0xa7, 0xe5, 0xd7, 0x3c, 0xec, 0xb4, 0xf0, 0x22, + 0x38, 0x2d, 0x32, 0x8e, 0xd3, 0xdf, 0x09, 0x9c, 0x72, 0x03, 0x00, + 0x78, 0x06, 0xe2, 0xe8, 0xfc, 0x54, 0x08, 0x7c, 0xa0, 0x72, 0x5c, + 0x96, 0x73, 0x02, 0x69, 0x40, 0xe3, 0x8f, 0xe8, 0x68, 0xad, 0x37, + 0x82, 0x5b, 0xdc, 0xdc, 0x2c, 0xee, 0x08, 0x83, 0xea, 0x8c, 0x1e, + 0x83, 0x89, 0x6f, 0xfc, 0x69, 0xb7, 0xfc, 0xf3, 0x9c, 0xe2, 0x29, + 0xd9, 0x4a, 0xa9, 0x58, 0x1a, 0xe6, 0xfd, 0x46, 0xbe, 0x3a, 0x95, + 0x5f, 0xfd, 0x57, 0xb8, 0x42, 0x4a, 0xdc, 0x88, 0xad, 0x7d, 0x06, + 0xbf, 0x4a, 0xe0, 0xd2, 0xea, 0xcf, 0x05, 0x6b, 0x45, 0xe7, 0x92, + 0x67, 0x77, 0x1f, 0xcf, 0xff, 0x6a, 0x71, 0x7a, 0x2f, 0x68, 0xb6, + 0x6b, 0x4a, 0xb6, 0x3b, 0xd0, 0x3d, 0x26, 0x38, 0xf4, 0xed, 0x56, + 0xbb, 0x35, 0xcd, 0xb8, 0xf8, 0x9f, 0x27, 0x6c, 0x93, 0x20, 0x84, + 0x0e, 0x48, 0xde, 0x1d, 0x8e, 0x2f, 0x81, 0x17, 0x2f, 0xc5, 0xee, + 0x6a, 0x52, 0x42, 0x16, 0xca, 0x32, 0x8c, 0xef, 0x17, 0xde, 0x6e, + 0x41, 0x61, 0x2e, 0xcc, 0x35, 0x76, 0x62, 0x1f, 0x90, 0xd9, 0x4b, + 0xfc, 0xb4, 0x72, 0xcc, 0x3d, 0x2c, 0x5b, 0x14, 0x72, 0xe1, 0x99, + 0xc6, 0x6c, 0x04, 0xc0, 0x3a, 0xef, 0x01, 0xc7, 0x15, 0xea, 0xdb, + 0x62, 0x17, 0x9d, 0xb4, 0xe3, 0xbb, 0x83, 0x05, 0x6e, 0x1b, 0x30, + 0x89, 0x80, 0x56, 0x48, 0x66, 0x9c, 0xea, 0x99, 0x0e, 0x40, 0xda, + 0x44, 0xb8, 0x61, 0xa7, 0x0c, 0xa4, 0xc3, 0xa9, 0x15, 0x5d, 0xdc, + 0x80, 0x5b, 0x42, 0x0b, 0x9e, 0xd1, 0xb1, 0x40, 0x36, 0xf1, 0x41, + 0x1b, 0x4d, 0x4d, 0x70, 0xc5, 0x42, 0xaa, 0x31, 0xaf, 0xfe, 0x2c, + 0xfd, 0x28, 0x76, 0x52, 0xfa, 0x31, 0x98, 0x94, 0x7e, 0x76, 0x25, + 0x0f, 0x89, 0x88, 0x0e, 0xfd, 0xc5, 0x50, 0x60, 0x43, 0x99, 0xfa, + 0xfa, 0x99, 0x86, 0x3f, 0xa6, 0x4a, 0xbb, 0x7d, 0xf5, 0xfa, 0x8a, + 0xf3, 0xe9, 0x1b, 0x04, 0xd3, 0xf9, 0x4e, 0x40, 0x5b, 0x9a, 0xf7, + 0x0c, 0x55, 0x4d, 0xe8, 0x3c, 0x75, 0x34, 0x00, 0x6c, 0x19, 0xb9, + 0x59, 0x2c, 0xb8, 0xd0, 0xcd, 0x4a, 0x03, 0xa4, 0x61, 0x97, 0xe7, + 0xfa, 0xcd, 0x83, 0x8e, 0x62, 0xc7, 0x47, 0xd3, 0x57, 0xd3, 0x04, + 0x51, 0x54, 0xc1, 0x79, 0x89, 0x0a, 0x87, 0xfd, 0x18, 0xd1, 0x35, + 0xdc, 0x9a, 0xc8, 0x28, 0x9d, 0x4b, 0x14, 0x87, 0x2b, 0x83, 0xf3, + 0x82, 0x8b, 0x03, 0x23, 0x6a, 0x27, 0xc0, 0xc0, 0x82, 0x51, 0xd3, + 0xc9, 0x95, 0x3c, 0x88, 0x8d, 0x63, 0x54, 0x7f, 0x13, 0xcb, 0xbc, + 0xac, 0xa7, 0xe4, 0x7a, 0xe0, 0xc6, 0xfa, 0x70, 0x2b, 0x8c, 0x3f, + 0x6c, 0x49, 0xef, 0x64, 0x91, 0xc4, 0x69, 0xc1, 0xf2, 0x4a, 0x5a, + 0x44, 0x40, 0x1f, 0x6a, 0x07, 0x70, 0x3d, 0xc7, 0xf2, 0x44, 0x30, + 0xae, 0x38, 0xcc, 0x76, 0xc2, 0x4b, 0xfb, 0xd8, 0xa1, 0x2f, 0x34, + 0xcf, 0x44, 0x7c, 0xc5, 0xe2, 0x74, 0x61, 0x98, 0xb0, 0x70, 0x11, + 0x00, 0xe2, 0x32, 0xc7, 0x1d, 0x0f, 0x96, 0x7c, 0x4c, 0xe3, 0x0e, + 0x51, 0xc7, 0x81, 0x0c, 0xe2, 0xe8, 0x79, 0x89, 0x36, 0x52, 0xa2, + 0xef, 0x29, 0x0e, 0xa6, 0x26, 0x26, 0x57, 0xe2, 0xc7, 0x5f, 0x9d, + 0x9c, 0x48, 0x6d, 0x01, 0x02, 0x5b, 0x4f, 0x94, 0x22, 0x80, 0xd7, + 0xb3, 0x74, 0x0d, 0xcb, 0xd1, 0x70, 0x5e, 0xbb, 0x48, 0x97, 0xe5, + 0xc1, 0x07, 0x81, 0xcf, 0x04, 0xe1, 0xfc, 0x6b, 0x60, 0xf3, 0x71, + 0xb8, 0xe0, 0xfd, 0x16, 0xd1, 0x7b, 0x44, 0xf9, 0x42, 0x71, 0xb8, + 0x96, 0x46, 0x4e, 0x11, 0x6c, 0x50, 0x97, 0x3c, 0x02, 0x3f, 0x66, + 0x5b, 0xc9, 0xf5, 0xe2, 0x81, 0x7c, 0x42, 0xd9, 0x95, 0xe6, 0x43, + 0x09, 0xc5, 0x65, 0x24, 0xc9, 0x76, 0x43, 0x78, 0xc0, 0x1e, 0xb1, + 0x4b, 0x6e, 0xba, 0x2f, 0xa6, 0x00, 0x6c, 0xdf, 0xf1, 0xd0, 0x5e, + 0x35, 0x8e, 0xb9, 0x05, 0x52, 0x0d, 0xcd, 0xf6, 0x36, 0xab, 0x61, + 0x2e, 0xb2, 0x69, 0xfb, 0x1c, 0x1f, 0x05, 0xd0, 0x9a, 0xf2, 0xb7, + 0xef, 0xa0, 0xfc, 0x2d, 0x48, 0x79, 0x60, 0x96, 0x5b, 0x46, 0x5b, + 0x95, 0xde, 0x39, 0x26, 0x29, 0xe3, 0xc3, 0x54, 0x9e, 0x6e, 0x61, + 0x8f, 0x39, 0x25, 0x4d, 0x93, 0x21, 0x1b, 0x92, 0x2c, 0xca, 0xba, + 0x0b, 0x81, 0x22, 0x54, 0x1c, 0x5e, 0xe8, 0x81, 0x7a, 0xa4, 0x9a, + 0x1c, 0x96, 0x68, 0xf8, 0xf1, 0x8b, 0xee, 0xe3, 0x1a, 0x57, 0xc8, + 0xd9, 0x90, 0xe3, 0xb4, 0xe6, 0x68, 0x89, 0xab, 0x33, 0xd8, 0xd5, + 0xb8, 0x03, 0x0d, 0x38, 0x50, 0xcf, 0x26, 0x13, 0x50, 0x52, 0x6d, + 0x94, 0x1d, 0xc6, 0xa0, 0xd6, 0xb9, 0x7a, 0xb6, 0xe8, 0x4a, 0xde, + 0xd9, 0xa6, 0x2b, 0x2d, 0x89, 0xfb, 0x2c, 0x1c, 0xd3, 0x73, 0x20, + 0xf2, 0xc7, 0xe7, 0x2a, 0x8d, 0x94, 0xff, 0xc8, 0x1f, 0x4d, 0xc0, + 0x97, 0x30, 0x63, 0x88, 0x17, 0x92, 0xce, 0x66, 0x3d, 0xc6, 0x1c, + 0xe1, 0x3b, 0x82, 0xd7, 0x8c, 0xd9, 0x2a, 0x24, 0xc0, 0x9e, 0xd4, + 0x80, 0x2b, 0x9c, 0x49, 0x2f, 0x04, 0x3b, 0x2c, 0x78, 0x46, 0xef, + 0xb1, 0xd0, 0xe9, 0x4e, 0xe8, 0xcd, 0x85, 0xea, 0xea, 0xed, 0xb9, + 0xe2, 0x04, 0x0c, 0x72, 0xa2, 0x0b, 0xb2, 0x0c, 0x45, 0x01, 0x5d, + 0xfc, 0x79, 0xfe, 0x2d, 0xe2, 0xd9, 0x4d, 0xf5, 0x4b, 0x96, 0xb3, + 0x5d, 0xed, 0xac, 0x7e, 0x78, 0x75, 0x79, 0x7d, 0x7e, 0x72, 0xe7, + 0x35, 0x4b, 0xac, 0x2e, 0x37, 0xbc, 0xa0, 0x99, 0xe3, 0x92, 0x7b, + 0xf7, 0x3b, 0x11, 0xaf, 0x9c, 0x60, 0x99, 0xec, 0x0d, 0x8c, 0xaf, + 0x41, 0x2f, 0x78, 0x3d, 0x9e, 0xb1, 0x5d, 0xe9, 0x66, 0xb8, 0x1d, + 0xbf, 0x70, 0x8d, 0x4b, 0x15, 0x57, 0x0e, 0xb3, 0xf3, 0xbb, 0xcb, + 0x8b, 0x43, 0x07, 0xa3, 0xa9, 0xae, 0xb8, 0x4e, 0xbf, 0x71, 0x22, + 0xc2, 0x6f, 0xf7, 0xa4, 0x6e, 0x6b, 0xef, 0x3f, 0xdb, 0x86, 0xe1, + 0x85, 0x26, 0x73, 0x7a, 0xfe, 0xfe, 0xf2, 0xfa, 0xd0, 0x5f, 0xaa, + 0xe4, 0xdb, 0x6b, 0xe1, 0x52, 0xca, 0xe6, 0x30, 0xb0, 0x8c, 0x68, + 0x08, 0xcc, 0xc8, 0x9d, 0xf4, 0x9b, 0xbd, 0x65, 0xac, 0xa8, 0x9a, + 0xae, 0x08, 0x9a, 0x38, 0x6e, 0xa9, 0xfb, 0xdc, 0x71, 0x31, 0x7d, + 0x80, 0x66, 0xa1, 0x91, 0x62, 0xd0, 0x8a, 0xb9, 0xbd, 0x3a, 0xe9, + 0xf3, 0xa9, 0xf9, 0x10, 0x9d, 0x23, 0x72, 0xaa, 0x32, 0x4a, 0x3f, + 0x9d, 0x7b, 0x4f, 0x49, 0x5b, 0xc7, 0xab, 0xf9, 0x11, 0xc5, 0xe7, + 0xb8, 0xda, 0xd3, 0x29, 0x3d, 0xe3, 0x75, 0xb9, 0x53, 0x47, 0x26, + 0x4b, 0x48, 0xa7, 0x82, 0x0c, 0xd7, 0xe2, 0x97, 0x6b, 0xae, 0x98, + 0x99, 0xee, 0x2a, 0x06, 0x8a, 0x8a, 0x41, 0xb3, 0x2c, 0x11, 0xcb, + 0x28, 0xaa, 0x11, 0x64, 0x59, 0x3a, 0x82, 0x29, 0xd0, 0x2c, 0x20, + 0xde, 0x4b, 0x9e, 0x2a, 0x6f, 0xf9, 0xba, 0x30, 0x47, 0x19, 0x4f, + 0x1e, 0xfd, 0x32, 0x9f, 0x33, 0xa3, 0xa2, 0x8f, 0x8c, 0x05, 0xaa, + 0xd2, 0xe4, 0xaf, 0xec, 0x81, 0xf1, 0x14, 0x62, 0x3f, 0x4c, 0x85, + 0xe1, 0xfd, 0x38, 0x93, 0x94, 0x58, 0x64, 0xaf, 0xb5, 0x68, 0x72, + 0x97, 0x71, 0x89, 0x91, 0x03, 0xef, 0xa5, 0x39, 0xfb, 0xe0, 0xe3, + 0xf2, 0x28, 0x0b, 0x1f, 0x97, 0x47, 0x59, 0x05, 0x8d, 0xcb, 0xa3, + 0xac, 0x50, 0x3a, 0x35, 0x98, 0x4f, 0x7c, 0x03, 0xe9, 0x68, 0x8f, + 0x4a, 0xba, 0x1c, 0xf8, 0x20, 0x26, 0xee, 0x63, 0xc5, 0x28, 0x26, + 0xc9, 0x8a, 0x72, 0x18, 0x23, 0x12, 0x3a, 0x4b, 0xd8, 0x31, 0x8e, + 0xfd, 0x7b, 0x1c, 0x97, 0xd0, 0x59, 0xf8, 0xb8, 0x84, 0xce, 0xb2, + 0xd2, 0xb3, 0x3f, 0xc3, 0x4a, 0xe8, 0x14, 0x5d, 0xf1, 0x38, 0x28, + 0x4f, 0xd1, 0xe3, 0x1f, 0xa2, 0xbf, 0xd4, 0xe4, 0x7f, 0x9e, 0x95, + 0x25, 0xcd, 0x66, 0x20, 0xd2, 0x12, 0x57, 0x63, 0x7c, 0x63, 0x90, + 0xaf, 0x50, 0xe7, 0x6f, 0x74, 0xd3, 0x2f, 0xec, 0xb2, 0xaf, 0x6e, + 0xde, 0x85, 0xb8, 0x51, 0xa2, 0xe5, 0xf5, 0x1d, 0xd2, 0x8d, 0x7a, + 0x10, 0x5b, 0x22, 0x37, 0x0d, 0xc8, 0x20, 0x23, 0xa6, 0x92, 0xa1, + 0x2a, 0x29, 0x51, 0x94, 0x7a, 0x0c, 0x7c, 0x30, 0x13, 0xab, 0xef, + 0x29, 0xa1, 0x8f, 0x02, 0xad, 0x0a, 0x95, 0x69, 0x15, 0x78, 0xb1, + 0x68, 0x1b, 0xd4, 0xf5, 0xdc, 0x39, 0xf8, 0x8d, 0xf9, 0x1b, 0x7b, + 0x09, 0xad, 0x71, 0xd3, 0xe0, 0x8d, 0xf3, 0x96, 0xd9, 0x4c, 0x90, + 0xa1, 0x66, 0x85, 0x2b, 0xae, 0xc5, 0xa6, 0xb1, 0x06, 0x90, 0x60, + 0x48, 0x8c, 0x4b, 0xdb, 0x12, 0xd2, 0x91, 0xb4, 0x99, 0x98, 0xaf, + 0xd5, 0x90, 0x87, 0xec, 0x5c, 0xd7, 0xe9, 0xc7, 0x03, 0x79, 0x91, + 0x0d, 0x8e, 0xc7, 0xd8, 0xd0, 0xe8, 0x2c, 0x38, 0x38, 0x73, 0xb2, + 0x1d, 0x84, 0xb4, 0xba, 0x02, 0xf9, 0xa8, 0x3f, 0x30, 0x0b, 0x70, + 0xee, 0x0f, 0xcb, 0x02, 0xd3, 0x02, 0xcf, 0xbe, 0x47, 0x44, 0xff, + 0xc8, 0x79, 0xf3, 0x12, 0x80, 0x0e, 0x37, 0x48, 0x68, 0xf5, 0x1e, + 0xf7, 0xcd, 0xdc, 0xf6, 0x4c, 0xdc, 0xae, 0xe1, 0x06, 0x2f, 0x25, + 0x81, 0xcb, 0x6a, 0x41, 0x7e, 0x64, 0xea, 0x06, 0x37, 0x90, 0x4b, + 0xd2, 0xe9, 0xc2, 0x7f, 0x16, 0xa6, 0xf3, 0x92, 0x57, 0x4d, 0x47, + 0xaf, 0x78, 0x5d, 0x71, 0xf4, 0xfa, 0x25, 0x88, 0xcf, 0x06, 0x56, + 0x08, 0x16, 0xa5, 0xdb, 0x0f, 0x48, 0x36, 0xe2, 0x1c, 0xbd, 0x6a, + 0xed, 0x1e, 0xf9, 0x9d, 0xa7, 0x6e, 0xec, 0xd5, 0xab, 0x36, 0xf8, + 0x73, 0xf8, 0xe9, 0xe4, 0xea, 0xdf, 0xfe, 0xd4, 0x3c, 0x50, 0x6f, + 0x5c, 0xe1, 0x35, 0x66, 0xb2, 0x0e, 0x07, 0x09, 0xb9, 0x06, 0x2a, + 0x8a, 0x6b, 0x95, 0x7c, 0x44, 0x9b, 0xcf, 0x1b, 0x5e, 0x6f, 0x8a, + 0x56, 0xe7, 0x97, 0xd0, 0x6b, 0xff, 0xa8, 0xc6, 0xf3, 0x4d, 0x57, + 0xcd, 0x8e, 0xe3, 0x5b, 0xdb, 0xd2, 0x3e, 0xd6, 0xf9, 0x93, 0xe6, + 0xde, 0xd6, 0xd3, 0xe9, 0xc9, 0x86, 0x85, 0x25, 0xf6, 0x70, 0x8b, + 0xb7, 0xa8, 0x4c, 0xf8, 0x1d, 0x99, 0xfb, 0x73, 0x4d, 0xaf, 0x75, + 0x6d, 0x16, 0x1d, 0x47, 0xfd, 0xe0, 0x19, 0xef, 0x33, 0xc5, 0xab, + 0x41, 0xb4, 0xaf, 0x25, 0xee, 0xf4, 0xc3, 0x23, 0x47, 0x46, 0xe1, + 0x9c, 0x16, 0x77, 0x04, 0xa7, 0x1a, 0x0e, 0x6b, 0xb8, 0xbd, 0x25, + 0x8d, 0xbc, 0x68, 0x63, 0x27, 0x68, 0x61, 0x20, 0xae, 0x2c, 0x30, + 0xe8, 0xf2, 0x95, 0xe1, 0x38, 0x1d, 0xee, 0x2e, 0x36, 0x84, 0x8e, + 0x51, 0xbc, 0xc7, 0xad, 0x08, 0x05, 0xba, 0xd6, 0x71, 0x64, 0xd5, + 0x94, 0x7d, 0x9f, 0xd8, 0x17, 0xac, 0xa0, 0xf2, 0x28, 0xad, 0x6d, + 0xb5, 0x20, 0x64, 0x1c, 0xd4, 0x3a, 0xfd, 0x1c, 0xd9, 0x42, 0x09, + 0x88, 0x24, 0xd9, 0x09, 0x8f, 0xba, 0xa3, 0x40, 0x6b, 0x3b, 0xc6, + 0x49, 0x49, 0x72, 0xd8, 0x6b, 0x9a, 0xc7, 0xe2, 0x71, 0xba, 0x11, + 0x1e, 0x61, 0xc4, 0x4c, 0x80, 0x1c, 0xc6, 0x8f, 0x8f, 0x03, 0xa2, + 0xb5, 0xf9, 0xf4, 0xbe, 0x0c, 0x78, 0x6c, 0xea, 0xeb, 0x5f, 0xc6, + 0x31, 0xa6, 0xf7, 0xeb, 0x78, 0xca, 0x6c, 0xbf, 0xa7, 0xbc, 0xa5, + 0x64, 0x2b, 0xe2, 0xc9, 0x2e, 0x7e, 0xa8, 0x6d, 0x83, 0xce, 0x66, + 0x9e, 0x71, 0x1d, 0x76, 0xcd, 0x3c, 0x67, 0xf0, 0x9d, 0xd4, 0x37, + 0x08, 0xfb, 0x81, 0xef, 0x9e, 0x3a, 0xd9, 0x95, 0x6b, 0xad, 0x86, + 0xf8, 0x91, 0x0c, 0x35, 0x3d, 0xe3, 0x68, 0x7b, 0x1d, 0x49, 0xee, + 0x5e, 0xce, 0xdb, 0x13, 0x98, 0x2d, 0x66, 0xfe, 0x48, 0x3e, 0xcb, + 0x21, 0x4f, 0x48, 0x7f, 0x96, 0x5d, 0x58, 0x4c, 0xc2, 0x2e, 0x14, + 0x26, 0x61, 0x17, 0x99, 0xbc, 0x31, 0x36, 0xae, 0x6e, 0x12, 0x7c, + 0x80, 0x50, 0xc1, 0xa2, 0xd5, 0xa6, 0xe6, 0x66, 0x7c, 0xf1, 0x5a, + 0xac, 0x6f, 0xb2, 0xe3, 0x30, 0x76, 0xdd, 0x36, 0xec, 0x5c, 0xf0, + 0x25, 0xc6, 0xe4, 0x1a, 0xdb, 0x67, 0x6c, 0x05, 0xeb, 0x7e, 0x4b, + 0x54, 0x1e, 0x4d, 0xd8, 0x5d, 0xd5, 0xeb, 0x55, 0xa3, 0xfa, 0xe3, + 0x25, 0x22, 0x6b, 0xc4, 0xe8, 0x2e, 0x78, 0x70, 0xb3, 0xee, 0x6e, + 0x57, 0xd0, 0x9a, 0xfb, 0x73, 0x3f, 0xd9, 0x18, 0x8f, 0xa6, 0x69, + 0x7d, 0xc7, 0x18, 0x75, 0x8a, 0x03, 0xaf, 0x98, 0xac, 0x50, 0x44, + 0xfc, 0x13, 0x77, 0xd0, 0xac, 0x7f, 0x4b, 0x01, 0x2f, 0xc4, 0x35, + 0xae, 0xfd, 0x40, 0x5c, 0xb9, 0x14, 0x53, 0x38, 0x97, 0x60, 0x14, + 0x57, 0x53, 0xcc, 0xcf, 0x12, 0xb3, 0x05, 0x54, 0x6c, 0x2e, 0xd8, + 0x90, 0x96, 0xe6, 0xd6, 0x2f, 0x24, 0x67, 0x82, 0xbc, 0x26, 0x63, + 0xbc, 0x4a, 0xd5, 0x6e, 0xc4, 0xcf, 0x44, 0xb2, 0x7a, 0x85, 0x68, + 0xdf, 0x10, 0xfa, 0x11, 0x61, 0x12, 0xc2, 0x3f, 0xc6, 0x34, 0x7f, + 0xfc, 0x88, 0xbe, 0x73, 0x34, 0xce, 0x4c, 0xbd, 0x0b, 0xc4, 0xbf, + 0xc4, 0x64, 0x3d, 0xab, 0xc9, 0xdf, 0x92, 0x77, 0x28, 0xd2, 0x73, + 0x04, 0xd7, 0x9f, 0x70, 0xc1, 0xec, 0x3d, 0x52, 0x5e, 0x35, 0xbd, + 0x7c, 0x7d, 0xfc, 0xfa, 0xed, 0xab, 0xc7, 0xca, 0x54, 0xf2, 0x96, + 0x71, 0x6d, 0xf6, 0x9a, 0x85, 0x47, 0xaf, 0xdf, 0xbc, 0x7c, 0xf1, + 0xf6, 0x4d, 0x74, 0x0f, 0x8f, 0x31, 0xe7, 0x8f, 0x12, 0x8f, 0xa4, + 0x43, 0xa1, 0x17, 0x52, 0x09, 0xd1, 0x0f, 0xe4, 0x43, 0xd9, 0x57, + 0x79, 0x71, 0x37, 0x70, 0x63, 0xd8, 0x99, 0x62, 0x10, 0x9c, 0xc7, + 0xae, 0x5a, 0x82, 0x6e, 0x94, 0xfc, 0x75, 0x85, 0xf3, 0x95, 0x1c, + 0x89, 0x78, 0x7d, 0x2e, 0x51, 0xb9, 0x04, 0xd6, 0xf8, 0xd0, 0xcd, + 0x74, 0xa8, 0x9e, 0xf9, 0x04, 0xdb, 0x81, 0xda, 0x61, 0x2c, 0xec, + 0x10, 0x2c, 0xd2, 0x63, 0xd4, 0x0e, 0xa8, 0x44, 0xd5, 0x52, 0x19, + 0x51, 0xa2, 0x6f, 0x2c, 0xfc, 0xe3, 0xf9, 0x96, 0xeb, 0x8a, 0xc9, + 0x21, 0xb6, 0xc7, 0x2e, 0x2f, 0xb4, 0xf6, 0xde, 0xd1, 0x9a, 0x9a, + 0x3b, 0x5a, 0xc3, 0x2a, 0x79, 0x36, 0xbc, 0xae, 0x1d, 0x8e, 0x27, + 0xd3, 0xc5, 0x30, 0x8d, 0x8a, 0x6a, 0x92, 0x74, 0xe3, 0xd8, 0x70, + 0xa6, 0xc5, 0x69, 0xb9, 0x83, 0xd8, 0xd8, 0xcd, 0x95, 0x69, 0xaf, + 0xfc, 0x90, 0x4b, 0xf6, 0x39, 0xe7, 0xf9, 0x4a, 0xdc, 0x2f, 0x9a, + 0xeb, 0x86, 0xbb, 0xbc, 0x54, 0x97, 0xcf, 0x78, 0x70, 0x0e, 0x2d, + 0xa4, 0xdd, 0x97, 0x9b, 0xcb, 0xa5, 0xab, 0xbf, 0xfe, 0x65, 0xf3, + 0x58, 0xd3, 0x2c, 0x74, 0x81, 0x7c, 0x5c, 0x4b, 0x0c, 0x80, 0x90, + 0x97, 0x75, 0xb7, 0x13, 0xce, 0xe5, 0xb4, 0xad, 0x98, 0x1c, 0x6c, + 0x7c, 0x0b, 0x2b, 0xb6, 0x16, 0xf3, 0xf2, 0xc1, 0x19, 0x69, 0xf3, + 0xd5, 0xbe, 0x50, 0x64, 0x24, 0x19, 0xce, 0x88, 0x95, 0x33, 0x59, + 0x61, 0xa6, 0xba, 0x33, 0xfb, 0x02, 0x01, 0x07, 0xee, 0xaf, 0x53, + 0xa7, 0x12, 0x37, 0xd3, 0x63, 0x8a, 0x7d, 0x1b, 0x8f, 0xef, 0xdb, + 0x7c, 0x7e, 0xb3, 0x41, 0x34, 0x26, 0x15, 0x26, 0x44, 0xe0, 0x67, + 0xca, 0x83, 0xe3, 0x13, 0xbc, 0x04, 0x0c, 0xc6, 0xdd, 0x15, 0x06, + 0xa3, 0xaf, 0x5f, 0x83, 0x7e, 0x69, 0x63, 0x0e, 0x66, 0xe2, 0x0a, + 0x36, 0xf0, 0xef, 0x18, 0x07, 0x2f, 0x19, 0x77, 0x57, 0xba, 0xdd, + 0xd3, 0x6c, 0x17, 0x98, 0xc4, 0x4e, 0xb6, 0xe6, 0x2a, 0x74, 0xcd, + 0x22, 0x74, 0xf6, 0xba, 0x65, 0xe4, 0x1b, 0x4e, 0xf7, 0x6b, 0xd4, + 0x32, 0xae, 0x77, 0xe8, 0x6d, 0x3e, 0x3c, 0xe2, 0x00, 0xfe, 0x45, + 0xe8, 0x71, 0x6f, 0xb8, 0x97, 0xa1, 0x23, 0xd3, 0x91, 0x7b, 0x8c, + 0xbe, 0x6a, 0xff, 0x1a, 0x6d, 0xfa, 0xc6, 0xdb, 0xbe, 0x4e, 0x37, + 0x4f, 0x32, 0x72, 0xb3, 0x10, 0xf4, 0x24, 0x7f, 0x3a, 0xe6, 0x27, + 0xb2, 0xb9, 0x3f, 0xdb, 0x75, 0xe0, 0xaa, 0x4c, 0x34, 0xa3, 0xeb, + 0xdf, 0x97, 0x19, 0x21, 0x66, 0x53, 0x97, 0xba, 0xfa, 0x0f, 0x88, + 0x2c, 0x33, 0x71, 0x71, 0xf0, 0x2b, 0x55, 0xea, 0xf7, 0x76, 0x22, + 0x65, 0xa7, 0x10, 0x46, 0xd8, 0x9b, 0x39, 0x59, 0x65, 0xa0, 0x23, + 0x7c, 0x6f, 0x9b, 0xca, 0xfb, 0xca, 0x7f, 0x6f, 0xea, 0x94, 0xee, + 0xe8, 0x45, 0xdb, 0x79, 0xf6, 0xf7, 0x04, 0x40, 0xb4, 0xf7, 0xa6, + 0x76, 0xb5, 0xb7, 0xda, 0xb6, 0xa5, 0xf6, 0x3e, 0xff, 0xd4, 0xdb, + 0xf1, 0x27, 0x42, 0xcb, 0xc2, 0x4e, 0x15, 0x16, 0xfd, 0x9a, 0x16, + 0x8b, 0x9f, 0xbb, 0x19, 0x84, 0x55, 0x07, 0xc3, 0x8c, 0x9f, 0x44, + 0xae, 0x03, 0x4d, 0xec, 0x7f, 0x09, 0x6f, 0xb6, 0x7d +}; + +const int standard_nodes_data_len = 2847; + diff --git a/pandatool/src/vrml/standard_nodes.cxx b/pandatool/src/vrml/standard_nodes.cxx new file mode 100644 index 00000000..221efe14 --- /dev/null +++ b/pandatool/src/vrml/standard_nodes.cxx @@ -0,0 +1,47 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file standard_nodes.cxx + * @author drose + * @date 2004-10-01 + */ + +#include "standard_nodes.h" + +// The binary data included here was generated from standardNodes.wrl (in this +// directory) file via the utility program bin2c (defined in pandatool). It +// contains the set of VRML definitions that must be loaded before any +// standard VRML file can be properly interpreted. + +#ifndef CPPPARSER + +#if defined(HAVE_ZLIB) + +// If we have zlib available, we can store this file compressed, which is much +// smaller. + +// Regenerate this file with: + +// pcompress standardNodes.wrl standardNodes.wrl.pz bin2c -n +// standard_nodes_data -o standardNodes.wrl.pz.c standardNodes.wrl.pz + +#include "standardNodes.wrl.pz.c" + +#else // HAVE_ZLIB + +// If we don't have zlib, just include the whole uncompressed file. + +// Regenerate this file with: + +// bin2c -n standard_nodes_data -o standardNodes.wrl.c standardNodes.wrl + +#include "standardNodes.wrl.c" + +#endif // HAVE_ZLIB + +#endif // CPPPARSER diff --git a/pandatool/src/vrml/standard_nodes.h b/pandatool/src/vrml/standard_nodes.h new file mode 100644 index 00000000..e97101d6 --- /dev/null +++ b/pandatool/src/vrml/standard_nodes.h @@ -0,0 +1,26 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file standard_nodes.h + * @author drose + * @date 2004-10-01 + */ + +#ifndef STANDARD_NODES_H +#define STANDARD_NODES_H + +#include "pandatoolbase.h" + +#ifndef CPPPARSER + +extern const unsigned char standard_nodes_data[]; +extern const int standard_nodes_data_len; + +#endif // CPPPARSER + +#endif diff --git a/pandatool/src/vrml/vrmlLexer.cxx.prebuilt b/pandatool/src/vrml/vrmlLexer.cxx.prebuilt new file mode 100644 index 00000000..8022d21f --- /dev/null +++ b/pandatool/src/vrml/vrmlLexer.cxx.prebuilt @@ -0,0 +1,4766 @@ +#line 2 "lex.yy.c" + +#line 4 "lex.yy.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer vrmlyy_create_buffer +#define yy_delete_buffer vrmlyy_delete_buffer +#define yy_flex_debug vrmlyy_flex_debug +#define yy_init_buffer vrmlyy_init_buffer +#define yy_flush_buffer vrmlyy_flush_buffer +#define yy_load_buffer_state vrmlyy_load_buffer_state +#define yy_switch_to_buffer vrmlyy_switch_to_buffer +#define yyin vrmlyyin +#define yyleng vrmlyyleng +#define yylex vrmlyylex +#define yylineno vrmlyylineno +#define yyout vrmlyyout +#define yyrestart vrmlyyrestart +#define yytext vrmlyytext +#define yywrap vrmlyywrap +#define yyalloc vrmlyyalloc +#define yyrealloc vrmlyyrealloc +#define yyfree vrmlyyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE vrmlyyrestart(vrmlyyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int vrmlyyleng; + +extern FILE *vrmlyyin, *vrmlyyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up vrmlyytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up vrmlyytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via vrmlyyrestart()), so that the user can continue scanning by + * just pointing vrmlyyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when vrmlyytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int vrmlyyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow vrmlyywrap()'s to do buffer switches + * instead of setting up a fresh vrmlyyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void vrmlyyrestart (FILE *input_file ); +void vrmlyy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE vrmlyy_create_buffer (FILE *file,int size ); +void vrmlyy_delete_buffer (YY_BUFFER_STATE b ); +void vrmlyy_flush_buffer (YY_BUFFER_STATE b ); +void vrmlyypush_buffer_state (YY_BUFFER_STATE new_buffer ); +void vrmlyypop_buffer_state (void ); + +static void vrmlyyensure_buffer_stack (void ); +static void vrmlyy_load_buffer_state (void ); +static void vrmlyy_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER vrmlyy_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE vrmlyy_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE vrmlyy_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE vrmlyy_scan_bytes (yyconst char *bytes,int len ); + +void *vrmlyyalloc (yy_size_t ); +void *vrmlyyrealloc (void *,yy_size_t ); +void vrmlyyfree (void * ); + +#define yy_new_buffer vrmlyy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + vrmlyyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + vrmlyy_create_buffer(vrmlyyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + vrmlyyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + vrmlyy_create_buffer(vrmlyyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +typedef unsigned char YY_CHAR; + +FILE *vrmlyyin = (FILE *) 0, *vrmlyyout = (FILE *) 0; + +typedef int yy_state_type; + +extern int vrmlyylineno; + +int vrmlyylineno = 1; + +extern char *vrmlyytext; +#define yytext_ptr vrmlyytext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up vrmlyytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + vrmlyyleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 48 +#define YY_END_OF_BUFFER 49 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[977] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 49, 47, 45, 46, 45, 14, + 45, 15, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 47, 47, 47, 47, 47, 47, 47, 47, + 24, 47, 47, 47, 47, 22, 22, 47, 47, 47, + 37, 35, 37, 37, 47, 47, 34, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 18, 19, 47, + + 47, 25, 47, 23, 23, 47, 47, 47, 38, 36, + 38, 38, 47, 47, 47, 47, 47, 47, 40, 40, + 41, 40, 40, 42, 47, 44, 44, 45, 45, 46, + 45, 45, 45, 14, 15, 14, 14, 7, 14, 14, + 14, 6, 14, 14, 14, 14, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, + 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, + 0, 37, 37, 37, 16, 0, 34, 34, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 17, 0, 25, + + 25, 0, 23, 23, 0, 0, 0, 0, 0, 0, + 38, 38, 38, 17, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 40, 40, 40, 40, + 40, 39, 44, 44, 45, 4, 14, 14, 14, 14, + 5, 14, 14, 14, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, + 43, 43, 0, 0, 0, 0, 43, 43, 0, 22, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 34, 0, 0, 26, 0, 0, 0, 0, + 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 25, 23, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 27, 0, 0, 0, 0, 0, 27, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 44, 45, 14, 9, 14, 14, 14, 14, + 14, 0, 20, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 43, 43, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 26, 0, 26, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 27, 0, 27, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 45, 14, 2, + 8, 14, 14, 12, 21, 0, 0, 32, 0, 0, + 0, 0, 0, 32, 0, 0, 0, 0, 32, 0, + 0, 0, 32, 0, 0, 0, 0, 0, 32, 0, + 0, 0, 43, 43, 43, 43, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 0, 0, 26, 0, 0, + + 28, 0, 0, 0, 0, 0, 28, 0, 0, 0, + 0, 28, 0, 0, 0, 28, 0, 0, 0, 0, + 0, 28, 0, 0, 0, 0, 0, 33, 0, 0, + 0, 0, 0, 33, 0, 0, 0, 0, 33, 0, + 0, 0, 33, 0, 0, 0, 0, 0, 33, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 27, 0, 0, 27, 0, 0, 29, 0, 0, 0, + 0, 0, 29, 0, 0, 0, 0, 29, 0, 0, + + 0, 29, 0, 0, 0, 0, 0, 29, 0, 0, + 0, 45, 14, 14, 14, 14, 32, 0, 32, 0, + 32, 0, 32, 0, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 28, 0, 28, + 0, 28, 0, 28, 0, 28, 0, 33, 0, 33, + 0, 33, 0, 33, 0, 33, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, + 29, 0, 29, 0, 29, 0, 29, 0, 45, 14, + + 10, 14, 14, 0, 32, 0, 0, 32, 0, 0, + 32, 0, 32, 0, 0, 32, 0, 0, 30, 0, + 0, 0, 0, 0, 30, 0, 0, 0, 0, 30, + 0, 0, 30, 0, 0, 0, 0, 0, 30, 0, + 0, 0, 0, 0, 30, 0, 0, 0, 30, 0, + 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, + 0, 0, 30, 0, 0, 0, 0, 28, 0, 0, + 28, 0, 0, 28, 0, 28, 0, 0, 28, 0, + 33, 0, 0, 33, 0, 0, 33, 0, 33, 0, + 0, 33, 0, 0, 31, 0, 0, 0, 0, 0, + + 31, 0, 0, 0, 0, 31, 0, 0, 31, 0, + 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, + 31, 0, 0, 0, 31, 0, 0, 0, 0, 0, + 31, 0, 0, 0, 0, 0, 0, 0, 31, 0, + 0, 0, 0, 29, 0, 0, 29, 0, 0, 29, + 0, 29, 0, 0, 29, 45, 14, 11, 14, 30, + 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, + 0, 30, 0, 30, 0, 30, 0, 31, 0, 31, + 0, 31, 0, 31, 0, 31, 0, 31, 0, 31, + 0, 31, 0, 31, 0, 45, 14, 14, 0, 30, + + 0, 0, 30, 0, 0, 30, 0, 30, 0, 0, + 30, 0, 0, 30, 0, 30, 0, 0, 30, 0, + 0, 30, 0, 31, 0, 0, 31, 0, 0, 31, + 0, 31, 0, 0, 31, 0, 0, 31, 0, 31, + 0, 0, 31, 0, 0, 31, 45, 14, 14, 45, + 3, 14, 45, 13, 45, 45, 45, 45, 1, 1, + 45, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 6, 7, 8, 6, 6, 6, 1, 6, + 6, 6, 9, 2, 10, 11, 6, 12, 13, 14, + 13, 13, 13, 13, 13, 15, 13, 6, 6, 6, + 6, 6, 6, 6, 16, 17, 17, 18, 19, 20, + 6, 6, 21, 6, 6, 22, 23, 24, 25, 26, + 6, 27, 28, 29, 30, 31, 6, 32, 6, 6, + 33, 34, 35, 6, 6, 6, 17, 17, 17, 36, + + 37, 38, 6, 6, 39, 6, 6, 40, 6, 41, + 42, 43, 6, 6, 44, 45, 46, 47, 6, 48, + 6, 6, 1, 6, 1, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6 + } ; + +static yyconst flex_int32_t yy_meta[49] = + { 0, + 1, 2, 3, 2, 2, 4, 5, 6, 1, 4, + 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 8, 1, 8, 7, 7, 7, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4 + } ; + +static yyconst flex_int16_t yy_base[1120] = + { 0, + 0, 7, 15, 0, 62, 90, 118, 0, 61, 74, + 89, 102, 130, 136, 142, 148, 162, 169, 174, 181, + 187, 193, 199, 205, 211, 237, 263, 289, 315, 341, + 367, 393, 427, 461, 487, 513, 539, 565, 226, 233, + 261, 278, 328, 354, 1038, 8632, 251, 0, 231, 0, + 1019, 0, 1006, 992, 985, 981, 983, 974, 972, 968, + 21, 946, 967, 954, 944, 66, 93, 599, 123, 166, + 278, 293, 635, 77, 304, 377, 319, 346, 332, 682, + 0, 8632, 391, 941, 372, 358, 404, 425, 398, 718, + 431, 437, 754, 442, 458, 790, 940, 8632, 8632, 463, + + 471, 476, 491, 497, 502, 524, 518, 826, 0, 8632, + 554, 929, 529, 551, 862, 556, 569, 898, 0, 586, + 8632, 6, 947, 8632, 607, 612, 616, 591, 935, 0, + 933, 932, 234, 0, 0, 914, 900, 0, 892, 883, + 877, 0, 886, 867, 855, 847, 857, 8632, 848, 620, + 0, 640, 934, 948, 649, 656, 661, 690, 698, 0, + 726, 962, 740, 702, 0, 737, 0, 763, 976, 990, + 770, 0, 806, 813, 0, 774, 835, 843, 873, 810, + 0, 878, 1004, 1018, 906, 882, 0, 911, 1032, 1046, + 918, 1050, 0, 1055, 1069, 1083, 1090, 8632, 1094, 1099, + + 1107, 1115, 1119, 0, 1125, 0, 1134, 1148, 1162, 1169, + 0, 1183, 1190, 0, 1187, 1224, 1192, 1260, 1274, 1199, + 1207, 1288, 1233, 1324, 1338, 1240, 0, 1304, 89, 280, + 306, 8632, 1244, 0, 271, 0, 852, 847, 839, 830, + 0, 811, 794, 792, 791, 797, 1302, 1306, 1352, 1388, + 1402, 0, 0, 1360, 1416, 1364, 1452, 1368, 1372, 1413, + 1426, 1417, 1473, 1487, 0, 0, 195, 0, 1440, 0, + 1457, 1437, 1501, 1537, 1551, 0, 0, 1509, 1565, 1513, + 1601, 1517, 1521, 1563, 1567, 1574, 1615, 0, 0, 0, + 1582, 442, 1586, 1629, 1634, 1638, 1652, 1688, 0, 0, + + 0, 1660, 1675, 1672, 1711, 1693, 1715, 1729, 1765, 0, + 0, 0, 1737, 1752, 1749, 1788, 1769, 1773, 0, 1793, + 1797, 1811, 1847, 0, 0, 0, 1819, 1834, 1831, 1870, + 1852, 1874, 1879, 1915, 0, 0, 0, 1887, 536, 1891, + 1929, 1896, 1900, 1943, 1979, 0, 0, 0, 1951, 1966, + 1963, 2002, 0, 160, 785, 0, 782, 787, 755, 755, + 760, 728, 8632, 1984, 2016, 2030, 2037, 2051, 2079, 2115, + 2129, 2059, 0, 2133, 2064, 2170, 2184, 2071, 2198, 2226, + 2262, 2276, 2290, 2087, 2095, 2326, 2362, 2376, 2412, 2426, + 2145, 2440, 2476, 2490, 2504, 2518, 2532, 2568, 2582, 2152, + + 2596, 2632, 2646, 2660, 2674, 2688, 2724, 2738, 2206, 2752, + 2788, 2802, 2816, 2830, 2210, 2218, 2844, 2880, 2894, 2930, + 2944, 2239, 2958, 2994, 3008, 3022, 3036, 119, 694, 0, + 0, 376, 652, 0, 8632, 2100, 2243, 2305, 3050, 3064, + 0, 0, 2247, 578, 2298, 3078, 3092, 3106, 3120, 3156, + 2335, 2339, 3170, 3206, 0, 0, 0, 2343, 586, 2347, + 2384, 3185, 0, 3222, 289, 560, 2389, 2393, 3269, 3305, + 3319, 0, 0, 2397, 2867, 2448, 3333, 3347, 3361, 3375, + 3411, 2453, 2457, 3425, 3461, 0, 0, 0, 2461, 3143, + 2540, 2544, 3475, 2548, 2552, 3511, 3525, 662, 2605, 2609, + + 3530, 3566, 3580, 0, 0, 2613, 663, 2617, 3594, 3608, + 3622, 3636, 3672, 2697, 2701, 3686, 3722, 0, 0, 0, + 2705, 669, 2709, 2760, 3701, 2765, 2769, 3728, 3764, 3778, + 0, 0, 2773, 709, 2852, 3792, 3806, 3820, 3834, 3870, + 2865, 2902, 3884, 3920, 0, 0, 0, 2906, 716, 2910, + 2914, 3899, 2967, 2971, 3935, 3971, 3985, 0, 0, 2975, + 3398, 2979, 3999, 4013, 4027, 4041, 4077, 3129, 3140, 4091, + 4127, 0, 0, 0, 3178, 3448, 3277, 3281, 4141, 3285, + 3289, 4177, 4191, 735, 3384, 3395, 4196, 4232, 4246, 0, + 0, 3433, 744, 3445, 4260, 4274, 4288, 4302, 4338, 3484, + + 3488, 4352, 4388, 0, 0, 0, 3492, 807, 3496, 3538, + 4367, 416, 653, 601, 560, 549, 3644, 3545, 4403, 4439, + 4453, 4489, 4503, 3652, 4539, 4575, 4589, 4625, 4639, 3694, + 4653, 4689, 4703, 4717, 4731, 4745, 4781, 4795, 4809, 4823, + 4837, 4873, 3736, 4887, 4923, 4937, 4951, 3842, 3743, 4965, + 5001, 5015, 5051, 5065, 3850, 5101, 5137, 3943, 3892, 5151, + 5187, 5201, 5237, 5251, 3951, 5287, 5323, 5337, 5373, 5387, + 4049, 5401, 5437, 5451, 5465, 5479, 5493, 5529, 5543, 5557, + 5571, 5585, 5621, 4056, 5635, 5671, 5685, 5699, 4099, 4107, + 5713, 5749, 5763, 5799, 5813, 4149, 5849, 5885, 413, 530, + + 0, 502, 501, 3549, 3656, 5899, 5913, 814, 5927, 5941, + 4325, 3747, 5955, 5969, 5983, 816, 3855, 3955, 4518, 5997, + 6011, 0, 0, 4060, 823, 4111, 6025, 6039, 6053, 6067, + 4154, 4158, 6103, 6139, 6153, 0, 0, 4162, 824, 4204, + 4208, 5080, 6167, 6181, 6195, 6231, 4213, 4217, 6245, 6281, + 0, 0, 0, 4310, 936, 4322, 4360, 5266, 4411, 6295, + 6309, 6323, 6337, 4415, 4419, 5828, 4423, 4461, 6373, 6387, + 1098, 6401, 6415, 4476, 4473, 6429, 6443, 6457, 1104, 4511, + 4547, 6471, 6485, 1149, 6499, 6513, 4562, 4559, 6527, 6541, + 6555, 1152, 4598, 4602, 6076, 6569, 6583, 0, 0, 4606, + + 1196, 4610, 6597, 6611, 6625, 6639, 4662, 4666, 6675, 6711, + 6725, 0, 0, 4670, 1205, 4674, 4753, 6648, 6739, 6753, + 6767, 6803, 4758, 4762, 6817, 6853, 0, 0, 0, 4766, + 1232, 4845, 4849, 6832, 4853, 6868, 6882, 6896, 6910, 4857, + 4895, 6919, 4899, 4903, 6955, 6969, 1261, 6983, 6997, 5038, + 4907, 7011, 7025, 7039, 1285, 246, 482, 0, 428, 5345, + 4973, 7053, 7089, 7103, 7139, 7153, 4980, 7189, 7225, 7239, + 7275, 7289, 5023, 7325, 7361, 7375, 7411, 5409, 5035, 5501, + 5073, 4985, 5109, 5114, 5121, 5593, 5159, 5164, 5171, 5210, + 5217, 5643, 5259, 5222, 5295, 633, 404, 385, 5299, 5303, + + 449, 471, 1701, 483, 509, 7425, 5307, 7461, 607, 647, + 1860, 656, 668, 7475, 5353, 7511, 682, 715, 1930, 751, + 761, 7525, 5357, 5417, 5421, 5509, 6084, 5513, 5601, 7168, + 5605, 7561, 5651, 5655, 6111, 5721, 5725, 7304, 5729, 5733, + 5771, 5775, 6203, 5779, 5783, 7434, 315, 395, 354, 263, + 0, 327, 601, 0, 726, 782, 1476, 1490, 5829, 5867, + 1604, 5872, 5877, 6127, 6221, 6261, 6268, 6355, 6273, 6360, + 6365, 6666, 6693, 6700, 6785, 8632, 7576, 7584, 7592, 7600, + 7608, 7616, 7621, 7625, 7632, 7640, 7648, 7655, 7663, 7671, + 7679, 7687, 7694, 328, 7701, 7709, 7717, 7725, 304, 7733, + + 7741, 7749, 7757, 7765, 7773, 238, 7781, 7786, 7793, 7801, + 7808, 168, 7815, 111, 7823, 7831, 83, 7839, 7847, 7855, + 7863, 7871, 7879, 7887, 7895, 7903, 7911, 7919, 7927, 7935, + 7943, 7951, 7959, 7967, 7972, 7979, 7987, 7995, 0, 8003, + 8011, 8019, 8027, 8035, 8043, 8051, 8059, 8067, 8075, 8083, + 8091, 8099, 8107, 8115, 8123, 8131, 8139, 8144, 8151, 8159, + 8167, 8175, 8183, 8191, 8199, 8207, 8215, 8223, 8231, 8239, + 8247, 8255, 8263, 8271, 8279, 8287, 8295, 8303, 8311, 8319, + 8327, 8335, 8343, 8348, 8355, 8363, 8371, 8379, 8387, 8395, + 8403, 8411, 8419, 8427, 8435, 8443, 8451, 8459, 8467, 8475, + + 8483, 8491, 8499, 8507, 8515, 8523, 8531, 8539, 8547, 8552, + 8559, 8567, 8575, 8583, 8591, 8599, 8607, 8615, 8623 + } ; + +static yyconst flex_int16_t yy_def[1120] = + { 0, + 977, 977, 976, 3, 977, 977, 977, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 978, 978, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 979, 979, 7, 7, 7, 7, 980, 980, + 980, 980, 7, 7, 976, 976, 976, 981, 982, 983, + 982, 984, 983, 983, 983, 983, 983, 983, 983, 983, + 983, 983, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 73, 976, 976, 976, 976, 976, 976, + 985, 976, 986, 985, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 976, 976, 976, 976, 976, 987, 976, + 988, 987, 976, 976, 976, 976, 976, 976, 989, 989, + 976, 990, 989, 976, 976, 976, 976, 976, 982, 981, + 982, 982, 982, 983, 984, 983, 983, 983, 983, 983, + 983, 983, 983, 983, 983, 983, 976, 976, 976, 976, + 68, 68, 976, 991, 976, 976, 976, 976, 976, 74, + 74, 992, 993, 976, 994, 976, 80, 80, 976, 995, + 976, 985, 986, 986, 985, 976, 976, 976, 976, 976, + 90, 90, 976, 996, 976, 976, 93, 93, 976, 997, + 976, 976, 96, 96, 976, 998, 976, 976, 976, 976, + + 976, 976, 976, 999, 976, 108, 108, 976, 1000, 976, + 987, 1001, 1001, 987, 976, 976, 216, 976, 1002, 976, + 976, 976, 222, 976, 1003, 976, 1004, 1004, 1005, 1005, + 1005, 976, 976, 1006, 1007, 1008, 1008, 1008, 1008, 1008, + 1008, 1008, 1008, 1008, 976, 976, 976, 976, 976, 1009, + 1009, 251, 251, 251, 251, 976, 976, 976, 976, 976, + 976, 976, 1010, 1010, 264, 264, 264, 264, 1011, 1012, + 976, 976, 976, 1013, 1013, 275, 275, 275, 275, 976, + 976, 976, 976, 976, 976, 976, 996, 287, 287, 287, + 287, 287, 976, 976, 976, 976, 976, 997, 298, 298, + + 298, 298, 298, 976, 976, 976, 976, 976, 998, 309, + 309, 309, 309, 309, 976, 976, 976, 976, 1014, 976, + 976, 976, 1000, 323, 323, 323, 323, 323, 976, 976, + 976, 976, 976, 1002, 334, 334, 334, 334, 334, 976, + 976, 976, 976, 976, 1003, 345, 345, 345, 345, 345, + 976, 976, 1006, 1007, 1008, 1008, 1008, 1008, 1008, 1008, + 1008, 976, 976, 249, 976, 1015, 976, 251, 368, 976, + 1016, 251, 1017, 264, 273, 976, 1018, 976, 275, 379, + 976, 1019, 379, 976, 976, 1020, 1020, 976, 976, 1021, + 976, 1022, 1022, 976, 1023, 1022, 976, 976, 1024, 976, + + 1025, 1025, 976, 1026, 1025, 976, 976, 1027, 976, 1028, + 1028, 976, 1029, 1028, 976, 976, 1030, 1030, 976, 976, + 1031, 976, 1032, 1032, 976, 1033, 1032, 1034, 1035, 1035, + 1035, 1035, 1035, 1035, 976, 976, 976, 976, 1036, 1036, + 440, 440, 440, 440, 976, 976, 1037, 1037, 448, 1038, + 976, 976, 976, 1038, 454, 454, 454, 454, 454, 448, + 448, 449, 1039, 976, 464, 464, 976, 976, 976, 1040, + 1040, 471, 471, 471, 471, 976, 976, 1041, 1041, 479, + 1042, 976, 976, 976, 1042, 485, 485, 485, 485, 485, + 479, 479, 479, 976, 976, 1043, 1043, 497, 976, 976, + + 976, 1044, 1044, 503, 503, 503, 503, 976, 976, 1045, + 1045, 511, 1046, 976, 976, 976, 1046, 517, 517, 517, + 517, 517, 511, 511, 512, 976, 976, 976, 1047, 1047, + 530, 530, 530, 530, 976, 976, 1048, 1048, 538, 1049, + 976, 976, 976, 1049, 544, 544, 544, 544, 544, 538, + 538, 539, 976, 976, 976, 1050, 1050, 557, 557, 557, + 557, 976, 976, 1051, 1051, 565, 1052, 976, 976, 976, + 1052, 571, 571, 571, 571, 571, 565, 565, 565, 976, + 976, 1053, 1053, 583, 976, 976, 976, 1054, 1054, 589, + 589, 589, 589, 976, 976, 1055, 1055, 597, 1056, 976, + + 976, 976, 1056, 603, 603, 603, 603, 603, 597, 597, + 598, 1057, 1058, 1058, 1058, 1058, 976, 976, 1059, 1059, + 1060, 1060, 976, 976, 1061, 1061, 976, 976, 1062, 976, + 1063, 1063, 976, 1064, 1063, 1065, 1065, 976, 1066, 1065, + 976, 1064, 976, 1067, 1067, 1066, 1067, 976, 976, 1068, + 1068, 1069, 1069, 976, 976, 1070, 1070, 976, 976, 1071, + 1071, 1072, 1072, 976, 976, 1073, 1073, 976, 976, 1074, + 976, 1075, 1075, 976, 1076, 1075, 1077, 1077, 976, 1078, + 1077, 976, 1076, 976, 1079, 1079, 1078, 1079, 976, 976, + 1080, 1080, 1081, 1081, 976, 976, 1082, 1082, 1083, 1084, + + 1084, 1084, 1084, 976, 976, 1085, 1085, 707, 1086, 1086, + 710, 976, 976, 1087, 1087, 715, 976, 976, 976, 1088, + 1088, 721, 721, 721, 721, 976, 976, 1089, 1089, 729, + 976, 976, 976, 1090, 1090, 735, 735, 735, 735, 729, + 729, 730, 1091, 1091, 744, 1092, 976, 976, 976, 1092, + 750, 750, 750, 750, 750, 744, 744, 745, 976, 976, + 1093, 1093, 762, 762, 762, 763, 976, 976, 1094, 1094, + 770, 1095, 1095, 773, 976, 976, 1096, 1096, 778, 976, + 976, 1097, 1097, 783, 1098, 1098, 786, 976, 976, 1099, + 1099, 791, 976, 976, 976, 1100, 1100, 797, 797, 797, + + 797, 976, 976, 1101, 1101, 805, 976, 976, 976, 1102, + 1102, 811, 811, 811, 811, 805, 805, 806, 1103, 1103, + 820, 1104, 976, 976, 976, 1104, 826, 826, 826, 826, + 826, 820, 820, 821, 976, 976, 1105, 1105, 838, 838, + 838, 839, 976, 976, 1106, 1106, 846, 1107, 1107, 849, + 976, 976, 1108, 1108, 854, 1109, 1110, 1110, 1110, 976, + 976, 1111, 1111, 1112, 1112, 976, 976, 1113, 1113, 1114, + 1114, 976, 976, 1115, 1115, 1116, 1116, 976, 976, 797, + 797, 806, 805, 809, 976, 811, 811, 821, 820, 825, + 976, 826, 826, 839, 838, 1109, 1110, 1110, 976, 976, + + 863, 863, 863, 865, 865, 865, 976, 976, 869, 869, + 869, 871, 871, 871, 976, 976, 875, 875, 875, 877, + 877, 877, 976, 976, 797, 797, 797, 805, 805, 806, + 976, 976, 811, 811, 811, 820, 820, 821, 976, 836, + 826, 826, 826, 838, 838, 839, 1109, 1110, 1110, 1109, + 1110, 1110, 1109, 1110, 1109, 1109, 1117, 1117, 976, 1117, + 1117, 976, 976, 1118, 1117, 976, 1117, 1119, 1118, 1118, + 1118, 1119, 1119, 1119, 1119, 0, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976 + } ; + +static yyconst flex_int16_t yy_nxt[8681] = + { 0, + 976, 47, 48, 47, 47, 976, 463, 49, 47, 48, + 47, 47, 131, 231, 49, 46, 47, 48, 47, 47, + 50, 46, 51, 46, 46, 46, 52, 52, 52, 52, + 50, 50, 53, 54, 50, 55, 50, 50, 56, 50, + 57, 58, 50, 59, 60, 50, 50, 46, 46, 46, + 50, 61, 62, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 47, 48, 47, 47, 144, 145, 51, + 69, 70, 71, 71, 71, 71, 150, 151, 151, 151, + 151, 63, 64, 69, 70, 71, 71, 71, 71, 463, + 65, 47, 48, 47, 47, 131, 231, 51, 72, 46, + + 73, 74, 74, 74, 152, 152, 152, 152, 976, 63, + 64, 72, 46, 73, 74, 74, 74, 319, 65, 47, + 48, 47, 47, 612, 976, 51, 132, 66, 67, 68, + 68, 68, 68, 156, 157, 157, 157, 157, 64, 75, + 46, 76, 77, 77, 77, 75, 46, 76, 77, 77, + 77, 78, 79, 80, 80, 80, 80, 78, 79, 80, + 80, 80, 80, 47, 48, 47, 47, 132, 82, 83, + 47, 48, 47, 47, 270, 82, 83, 158, 158, 158, + 158, 428, 84, 85, 86, 87, 87, 87, 87, 84, + 85, 86, 87, 87, 87, 87, 88, 89, 90, 90, + + 90, 90, 88, 89, 90, 90, 90, 90, 91, 92, + 93, 93, 93, 93, 91, 92, 93, 93, 93, 93, + 94, 95, 96, 96, 96, 96, 374, 120, 48, 120, + 120, 97, 121, 122, 120, 48, 120, 120, 132, 121, + 122, 132, 374, 98, 353, 99, 94, 95, 96, 96, + 96, 96, 128, 132, 128, 128, 896, 97, 129, 123, + 235, 133, 120, 48, 120, 120, 123, 124, 122, 98, + 132, 99, 100, 101, 102, 102, 102, 102, 132, 120, + 48, 120, 120, 97, 124, 122, 131, 231, 156, 157, + 157, 157, 157, 354, 123, 98, 159, 99, 100, 101, + + 102, 102, 102, 102, 160, 160, 160, 160, 953, 97, + 319, 123, 131, 231, 159, 164, 164, 164, 164, 950, + 263, 98, 132, 99, 103, 46, 104, 105, 105, 105, + 164, 164, 164, 164, 270, 97, 263, 125, 46, 126, + 127, 127, 127, 168, 168, 168, 168, 98, 46, 99, + 103, 46, 104, 105, 105, 105, 166, 167, 167, 167, + 167, 97, 954, 125, 46, 126, 127, 127, 127, 178, + 178, 178, 178, 98, 46, 99, 106, 107, 108, 108, + 108, 108, 176, 177, 177, 177, 177, 97, 164, 164, + 164, 164, 131, 952, 131, 131, 614, 131, 174, 98, + + 615, 99, 106, 107, 108, 108, 108, 108, 165, 182, + 182, 182, 182, 97, 176, 177, 177, 177, 177, 951, + 132, 949, 179, 132, 165, 98, 856, 99, 47, 48, + 47, 47, 948, 110, 111, 180, 181, 181, 181, 181, + 179, 186, 187, 187, 187, 187, 699, 112, 188, 188, + 188, 188, 192, 193, 193, 193, 193, 720, 723, 98, + 387, 99, 47, 48, 47, 47, 898, 110, 111, 194, + 194, 194, 194, 199, 200, 200, 200, 200, 387, 720, + 723, 112, 201, 201, 201, 201, 199, 200, 200, 200, + 200, 470, 473, 98, 202, 99, 113, 114, 115, 115, + + 115, 115, 203, 203, 203, 203, 897, 97, 203, 203, + 203, 203, 202, 203, 203, 203, 203, 470, 473, 98, + 859, 99, 113, 114, 115, 115, 115, 115, 204, 207, + 207, 207, 207, 97, 205, 206, 206, 206, 206, 215, + 216, 216, 216, 216, 204, 98, 858, 99, 116, 117, + 118, 118, 118, 118, 418, 131, 857, 131, 131, 97, + 131, 213, 217, 217, 217, 217, 221, 222, 222, 222, + 222, 98, 418, 99, 116, 117, 118, 118, 118, 118, + 223, 223, 223, 223, 703, 97, 131, 228, 131, 228, + 228, 263, 128, 229, 128, 128, 620, 98, 129, 99, + + 153, 153, 153, 153, 626, 702, 154, 263, 132, 150, + 151, 151, 151, 151, 620, 734, 737, 155, 233, 233, + 233, 233, 626, 233, 233, 233, 233, 233, 233, 233, + 233, 152, 152, 152, 152, 155, 161, 161, 161, 161, + 132, 701, 162, 234, 947, 955, 160, 160, 160, 160, + 976, 152, 152, 152, 152, 734, 737, 256, 256, 234, + 257, 257, 257, 257, 274, 277, 163, 158, 158, 158, + 158, 156, 157, 157, 157, 157, 274, 277, 700, 159, + 387, 651, 163, 169, 169, 169, 169, 657, 616, 170, + 750, 753, 166, 167, 167, 167, 167, 159, 387, 651, + + 171, 158, 158, 158, 158, 657, 258, 258, 159, 259, + 259, 259, 259, 164, 164, 164, 164, 613, 171, 183, + 183, 183, 183, 750, 753, 184, 159, 661, 180, 181, + 181, 181, 181, 132, 667, 260, 185, 261, 262, 262, + 262, 161, 161, 161, 161, 661, 435, 162, 168, 168, + 168, 168, 667, 418, 185, 189, 189, 189, 189, 485, + 488, 190, 692, 956, 186, 187, 187, 187, 187, 485, + 488, 418, 191, 976, 168, 168, 168, 168, 280, 280, + 692, 281, 281, 281, 281, 178, 178, 178, 178, 132, + 191, 195, 195, 195, 195, 434, 957, 196, 433, 432, + + 192, 193, 193, 193, 193, 431, 430, 131, 197, 131, + 131, 429, 131, 174, 131, 363, 131, 131, 362, 131, + 174, 182, 182, 182, 182, 698, 197, 208, 208, 208, + 208, 361, 620, 209, 626, 360, 205, 206, 206, 206, + 206, 863, 869, 698, 210, 176, 177, 177, 177, 177, + 620, 359, 626, 179, 178, 178, 178, 178, 358, 863, + 869, 179, 210, 218, 218, 218, 218, 357, 356, 219, + 355, 179, 215, 216, 216, 216, 216, 246, 245, 179, + 220, 282, 282, 244, 283, 283, 283, 283, 976, 182, + 182, 182, 182, 188, 188, 188, 188, 243, 220, 224, + + 224, 224, 224, 242, 241, 225, 240, 239, 221, 222, + 222, 222, 222, 238, 293, 293, 226, 294, 294, 294, + 294, 976, 188, 188, 188, 188, 304, 304, 237, 305, + 305, 305, 305, 236, 226, 153, 153, 153, 153, 132, + 132, 154, 132, 247, 248, 249, 249, 249, 249, 251, + 153, 251, 251, 232, 875, 252, 214, 253, 254, 255, + 255, 255, 255, 264, 161, 264, 264, 198, 175, 265, + 149, 266, 875, 267, 268, 268, 268, 169, 169, 169, + 169, 148, 147, 170, 146, 271, 272, 273, 273, 273, + 273, 275, 169, 275, 275, 143, 142, 276, 141, 277, + + 278, 279, 279, 279, 279, 183, 183, 183, 183, 140, + 139, 184, 138, 284, 285, 286, 286, 286, 286, 288, + 183, 288, 288, 137, 136, 289, 132, 290, 291, 292, + 292, 292, 292, 189, 189, 189, 189, 976, 976, 190, + 976, 295, 296, 297, 297, 297, 297, 299, 189, 299, + 299, 976, 976, 300, 976, 301, 302, 303, 303, 303, + 303, 194, 194, 194, 194, 976, 194, 194, 194, 194, + 195, 195, 195, 195, 976, 976, 196, 976, 306, 307, + 308, 308, 308, 308, 310, 195, 310, 310, 976, 976, + 311, 976, 312, 313, 314, 314, 314, 314, 315, 315, + + 976, 316, 316, 316, 316, 201, 201, 201, 201, 199, + 200, 200, 200, 200, 976, 976, 651, 202, 201, 201, + 201, 201, 657, 317, 317, 202, 318, 318, 318, 318, + 203, 203, 203, 203, 651, 202, 207, 207, 207, 207, + 657, 976, 976, 202, 976, 207, 207, 207, 207, 208, + 208, 208, 208, 976, 976, 209, 976, 320, 321, 322, + 322, 322, 322, 324, 208, 324, 324, 661, 976, 325, + 667, 326, 327, 328, 328, 328, 328, 329, 329, 976, + 330, 330, 330, 330, 131, 661, 131, 131, 667, 131, + 213, 131, 976, 131, 131, 976, 131, 213, 217, 217, + + 217, 217, 976, 217, 217, 217, 217, 340, 340, 976, + 341, 341, 341, 341, 881, 131, 976, 131, 223, 223, + 223, 223, 131, 887, 131, 218, 218, 218, 218, 976, + 976, 219, 881, 976, 215, 216, 216, 216, 216, 976, + 976, 887, 220, 976, 223, 223, 223, 223, 351, 351, + 893, 352, 352, 352, 352, 233, 233, 233, 233, 976, + 220, 218, 218, 218, 218, 976, 976, 219, 893, 331, + 332, 333, 333, 333, 333, 335, 218, 335, 335, 692, + 976, 336, 976, 337, 338, 339, 339, 339, 339, 224, + 224, 224, 224, 976, 976, 225, 976, 692, 221, 222, + + 222, 222, 222, 698, 976, 228, 226, 228, 228, 976, + 976, 229, 248, 249, 249, 249, 249, 364, 364, 364, + 364, 698, 976, 976, 226, 224, 224, 224, 224, 976, + 976, 225, 976, 342, 343, 344, 344, 344, 344, 346, + 224, 346, 346, 976, 976, 347, 976, 348, 349, 350, + 350, 350, 350, 365, 365, 365, 365, 976, 976, 366, + 976, 976, 248, 249, 249, 249, 249, 976, 976, 976, + 367, 368, 368, 368, 368, 257, 257, 257, 257, 259, + 259, 259, 259, 259, 259, 259, 259, 976, 367, 251, + 153, 251, 251, 976, 976, 252, 976, 253, 254, 255, + + 255, 255, 255, 251, 153, 251, 251, 976, 976, 252, + 976, 253, 254, 255, 255, 255, 255, 369, 370, 369, + 369, 976, 976, 371, 262, 262, 262, 262, 262, 262, + 262, 262, 976, 976, 372, 976, 976, 262, 262, 262, + 262, 161, 161, 161, 161, 976, 976, 162, 375, 375, + 375, 375, 372, 153, 153, 153, 153, 373, 976, 154, + 976, 976, 976, 257, 257, 257, 257, 272, 273, 273, + 273, 273, 976, 373, 264, 161, 264, 264, 959, 960, + 265, 976, 266, 961, 267, 268, 268, 268, 264, 161, + 264, 264, 959, 960, 265, 976, 266, 961, 267, 268, + + 268, 268, 376, 376, 376, 376, 976, 976, 377, 976, + 976, 272, 273, 273, 273, 273, 976, 976, 976, 378, + 379, 379, 379, 379, 281, 281, 281, 281, 283, 283, + 283, 283, 283, 283, 283, 283, 976, 378, 275, 169, + 275, 275, 976, 976, 276, 976, 277, 278, 279, 279, + 279, 279, 275, 169, 275, 275, 976, 976, 276, 976, + 277, 278, 279, 279, 279, 279, 380, 381, 380, 380, + 976, 976, 382, 285, 286, 286, 286, 286, 384, 384, + 384, 384, 976, 383, 285, 286, 286, 286, 286, 976, + 976, 976, 385, 386, 386, 386, 386, 294, 294, 294, + + 294, 383, 169, 169, 169, 169, 959, 960, 170, 976, + 385, 961, 281, 281, 281, 281, 288, 183, 288, 288, + 976, 976, 289, 976, 290, 291, 292, 292, 292, 292, + 183, 183, 183, 183, 976, 976, 184, 976, 976, 976, + 294, 294, 294, 294, 296, 297, 297, 297, 297, 388, + 388, 388, 388, 389, 389, 389, 389, 976, 976, 390, + 976, 976, 296, 297, 297, 297, 297, 976, 976, 976, + 391, 392, 392, 392, 392, 976, 393, 394, 393, 393, + 976, 976, 395, 305, 305, 305, 305, 976, 391, 299, + 189, 299, 299, 396, 976, 300, 976, 301, 302, 303, + + 303, 303, 303, 307, 308, 308, 308, 308, 976, 720, + 723, 396, 189, 189, 189, 189, 976, 976, 190, 863, + 976, 976, 305, 305, 305, 305, 397, 397, 397, 397, + 398, 398, 398, 398, 976, 976, 399, 863, 976, 307, + 308, 308, 308, 308, 976, 976, 976, 400, 401, 401, + 401, 401, 976, 402, 403, 402, 402, 976, 976, 404, + 316, 316, 316, 316, 976, 400, 310, 195, 310, 310, + 405, 976, 311, 976, 312, 313, 314, 314, 314, 314, + 318, 318, 318, 318, 318, 318, 318, 318, 405, 195, + 195, 195, 195, 976, 976, 196, 976, 976, 976, 316, + + 316, 316, 316, 321, 322, 322, 322, 322, 406, 406, + 406, 406, 407, 407, 407, 407, 976, 976, 408, 976, + 976, 321, 322, 322, 322, 322, 976, 976, 976, 409, + 410, 410, 410, 410, 976, 411, 412, 411, 411, 976, + 976, 413, 330, 330, 330, 330, 976, 409, 324, 208, + 324, 324, 414, 976, 325, 976, 326, 327, 328, 328, + 328, 328, 332, 333, 333, 333, 333, 976, 734, 737, + 414, 208, 208, 208, 208, 976, 976, 209, 869, 976, + 976, 330, 330, 330, 330, 415, 415, 415, 415, 332, + 333, 333, 333, 333, 976, 976, 869, 416, 417, 417, + + 417, 417, 341, 341, 341, 341, 343, 344, 344, 344, + 344, 419, 419, 419, 419, 416, 335, 218, 335, 335, + 976, 976, 336, 976, 337, 338, 339, 339, 339, 339, + 218, 218, 218, 218, 976, 976, 219, 976, 750, 753, + 341, 341, 341, 341, 420, 420, 420, 420, 875, 976, + 421, 976, 976, 343, 344, 344, 344, 344, 976, 976, + 976, 422, 423, 423, 423, 423, 875, 424, 425, 424, + 424, 976, 976, 426, 352, 352, 352, 352, 976, 422, + 346, 224, 346, 346, 427, 976, 347, 976, 348, 349, + 350, 350, 350, 350, 976, 364, 364, 364, 364, 976, + + 976, 976, 427, 224, 224, 224, 224, 976, 976, 225, + 976, 976, 976, 352, 352, 352, 352, 365, 365, 365, + 365, 976, 976, 366, 976, 436, 437, 438, 438, 438, + 438, 440, 365, 440, 440, 976, 976, 441, 976, 442, + 443, 444, 444, 444, 444, 445, 445, 976, 446, 446, + 446, 446, 369, 370, 369, 369, 976, 976, 371, 976, + 976, 976, 368, 368, 368, 368, 976, 460, 461, 372, + 462, 462, 462, 462, 976, 375, 375, 375, 375, 476, + 476, 976, 477, 477, 477, 477, 976, 372, 447, 448, + 449, 449, 449, 449, 976, 976, 976, 250, 384, 384, + + 384, 384, 976, 494, 494, 385, 495, 495, 495, 495, + 437, 438, 438, 438, 438, 250, 370, 370, 370, 370, + 976, 976, 450, 385, 451, 452, 453, 453, 453, 453, + 455, 370, 455, 455, 976, 976, 456, 976, 457, 458, + 459, 459, 459, 459, 464, 465, 465, 465, 466, 466, + 466, 466, 466, 508, 508, 976, 509, 509, 509, 509, + 535, 535, 976, 536, 536, 536, 536, 976, 466, 466, + 466, 376, 376, 376, 376, 976, 976, 377, 976, 467, + 468, 469, 469, 469, 469, 471, 376, 471, 471, 976, + 976, 472, 976, 473, 474, 475, 475, 475, 475, 380, + + 381, 380, 380, 976, 976, 382, 976, 976, 976, 379, + 379, 379, 379, 976, 562, 562, 383, 563, 563, 563, + 563, 415, 415, 415, 415, 976, 580, 580, 416, 581, + 581, 581, 581, 976, 383, 478, 479, 480, 480, 480, + 480, 976, 976, 976, 274, 976, 416, 594, 594, 976, + 595, 595, 595, 595, 617, 617, 617, 617, 619, 619, + 619, 619, 274, 381, 381, 381, 381, 976, 976, 481, + 976, 482, 483, 484, 484, 484, 484, 486, 381, 486, + 486, 976, 976, 487, 976, 488, 489, 490, 490, 490, + 490, 275, 169, 275, 275, 976, 976, 276, 491, 492, + + 976, 493, 493, 493, 493, 976, 976, 976, 274, 446, + 446, 446, 446, 976, 976, 437, 438, 438, 438, 438, + 976, 976, 976, 618, 976, 976, 274, 288, 183, 288, + 288, 976, 976, 289, 976, 290, 291, 386, 386, 386, + 386, 618, 976, 976, 387, 452, 453, 453, 453, 453, + 623, 623, 623, 623, 625, 625, 625, 625, 462, 462, + 462, 462, 387, 288, 183, 288, 288, 976, 976, 289, + 496, 497, 291, 498, 498, 498, 498, 389, 389, 389, + 389, 976, 976, 390, 976, 976, 976, 388, 388, 388, + 388, 976, 976, 976, 391, 462, 462, 462, 462, 468, + + 469, 469, 469, 469, 627, 627, 627, 627, 631, 631, + 631, 631, 391, 389, 389, 389, 389, 976, 976, 390, + 976, 499, 500, 501, 501, 501, 501, 503, 389, 503, + 503, 976, 976, 504, 976, 505, 506, 507, 507, 507, + 507, 393, 394, 393, 393, 976, 976, 395, 976, 301, + 302, 392, 392, 392, 392, 976, 976, 976, 396, 477, + 477, 477, 477, 483, 484, 484, 484, 484, 641, 641, + 641, 641, 644, 644, 644, 644, 396, 393, 394, 393, + 393, 976, 976, 395, 976, 510, 511, 512, 512, 512, + 512, 394, 394, 394, 394, 976, 976, 513, 976, 514, + + 515, 516, 516, 516, 516, 518, 394, 518, 518, 976, + 976, 519, 976, 520, 521, 522, 522, 522, 522, 299, + 189, 299, 299, 976, 976, 300, 523, 524, 302, 525, + 525, 525, 525, 398, 398, 398, 398, 976, 976, 399, + 976, 976, 976, 397, 397, 397, 397, 976, 976, 976, + 400, 493, 493, 493, 493, 493, 493, 493, 493, 495, + 495, 495, 495, 495, 495, 495, 495, 976, 400, 398, + 398, 398, 398, 976, 976, 399, 976, 526, 527, 528, + 528, 528, 528, 530, 398, 530, 530, 976, 976, 531, + 976, 532, 533, 534, 534, 534, 534, 402, 403, 402, + + 402, 976, 976, 404, 976, 312, 313, 401, 401, 401, + 401, 976, 976, 976, 405, 500, 501, 501, 501, 501, + 648, 648, 648, 648, 650, 650, 650, 650, 509, 509, + 509, 509, 405, 402, 403, 402, 402, 976, 976, 404, + 976, 537, 538, 539, 539, 539, 539, 403, 403, 403, + 403, 976, 976, 540, 976, 541, 542, 543, 543, 543, + 543, 545, 403, 545, 545, 976, 976, 546, 976, 547, + 548, 549, 549, 549, 549, 310, 195, 310, 310, 976, + 976, 311, 550, 551, 313, 552, 552, 552, 552, 407, + 407, 407, 407, 976, 976, 408, 976, 976, 976, 406, + + 406, 406, 406, 976, 976, 976, 409, 515, 516, 516, + 516, 516, 654, 654, 654, 654, 656, 656, 656, 656, + 525, 525, 525, 525, 409, 407, 407, 407, 407, 976, + 976, 408, 976, 553, 554, 555, 555, 555, 555, 557, + 407, 557, 557, 976, 976, 558, 976, 559, 560, 561, + 561, 561, 561, 411, 412, 411, 411, 976, 976, 413, + 976, 326, 327, 410, 410, 410, 410, 976, 976, 976, + 414, 525, 525, 525, 525, 527, 528, 528, 528, 528, + 658, 658, 658, 658, 660, 660, 660, 660, 414, 411, + 412, 411, 411, 976, 976, 413, 976, 564, 565, 566, + + 566, 566, 566, 412, 412, 412, 412, 976, 976, 567, + 976, 568, 569, 570, 570, 570, 570, 572, 412, 572, + 572, 976, 976, 573, 976, 574, 575, 576, 576, 576, + 576, 324, 208, 324, 324, 976, 976, 325, 577, 578, + 327, 579, 579, 579, 579, 335, 218, 335, 335, 976, + 976, 336, 976, 337, 338, 417, 417, 417, 417, 976, + 976, 976, 418, 536, 536, 536, 536, 976, 632, 633, + 632, 632, 976, 976, 634, 542, 543, 543, 543, 543, + 418, 335, 218, 335, 335, 635, 976, 336, 582, 583, + 338, 584, 584, 584, 584, 420, 420, 420, 420, 976, + + 976, 421, 976, 635, 976, 419, 419, 419, 419, 976, + 976, 976, 422, 664, 664, 664, 664, 666, 666, 666, + 666, 552, 552, 552, 552, 552, 552, 552, 552, 976, + 422, 420, 420, 420, 420, 976, 976, 421, 976, 585, + 586, 587, 587, 587, 587, 589, 420, 589, 589, 976, + 976, 590, 976, 591, 592, 593, 593, 593, 593, 424, + 425, 424, 424, 976, 976, 426, 976, 348, 349, 423, + 423, 423, 423, 976, 976, 976, 427, 554, 555, 555, + 555, 555, 668, 668, 668, 668, 672, 672, 672, 672, + 563, 563, 563, 563, 427, 424, 425, 424, 424, 976, + + 976, 426, 976, 596, 597, 598, 598, 598, 598, 425, + 425, 425, 425, 976, 976, 599, 976, 600, 601, 602, + 602, 602, 602, 604, 425, 604, 604, 976, 976, 605, + 976, 606, 607, 608, 608, 608, 608, 346, 224, 346, + 346, 976, 976, 347, 609, 610, 349, 611, 611, 611, + 611, 440, 365, 440, 440, 976, 976, 441, 976, 442, + 443, 444, 444, 444, 444, 440, 365, 440, 440, 976, + 976, 441, 976, 442, 443, 444, 444, 444, 444, 365, + 365, 365, 365, 976, 976, 366, 976, 976, 976, 446, + 446, 446, 446, 251, 153, 251, 251, 976, 976, 252, + + 976, 253, 448, 449, 449, 449, 449, 251, 153, 251, + 251, 976, 976, 252, 976, 253, 254, 621, 621, 621, + 621, 369, 370, 369, 369, 976, 976, 371, 976, 976, + 448, 449, 449, 449, 449, 976, 976, 976, 622, 569, + 570, 570, 570, 570, 645, 638, 645, 645, 976, 976, + 646, 682, 682, 682, 682, 976, 622, 455, 370, 455, + 455, 647, 976, 456, 976, 457, 458, 459, 459, 459, + 459, 365, 365, 365, 365, 976, 976, 366, 976, 647, + 452, 453, 453, 453, 453, 976, 976, 976, 624, 685, + 685, 685, 685, 976, 976, 254, 462, 462, 462, 462, + + 976, 976, 976, 372, 976, 976, 624, 455, 370, 455, + 455, 976, 976, 456, 976, 457, 458, 459, 459, 459, + 459, 372, 263, 264, 161, 264, 264, 263, 263, 265, + 263, 266, 263, 464, 465, 465, 465, 466, 466, 466, + 466, 466, 263, 263, 263, 263, 263, 263, 263, 263, + 263, 263, 263, 374, 263, 263, 263, 466, 466, 466, + 263, 263, 263, 263, 263, 263, 263, 263, 263, 374, + 628, 628, 628, 628, 976, 976, 629, 976, 976, 468, + 469, 469, 469, 469, 976, 976, 976, 630, 579, 579, + 579, 579, 579, 579, 579, 579, 581, 581, 581, 581, + + 581, 581, 581, 581, 976, 630, 471, 376, 471, 471, + 976, 976, 472, 976, 473, 474, 475, 475, 475, 475, + 471, 376, 471, 471, 976, 976, 472, 976, 473, 474, + 475, 475, 475, 475, 376, 376, 376, 376, 976, 976, + 377, 976, 976, 976, 477, 477, 477, 477, 275, 169, + 275, 275, 976, 976, 276, 976, 277, 479, 480, 480, + 480, 480, 275, 169, 275, 275, 976, 976, 276, 976, + 277, 278, 636, 636, 636, 636, 637, 638, 637, 637, + 976, 976, 639, 976, 976, 479, 480, 480, 480, 480, + 976, 976, 976, 640, 586, 587, 587, 587, 587, 673, + + 674, 673, 673, 976, 976, 675, 689, 689, 689, 689, + 976, 640, 486, 381, 486, 486, 676, 976, 487, 976, + 488, 489, 490, 490, 490, 490, 633, 633, 633, 633, + 976, 976, 642, 976, 676, 483, 484, 484, 484, 484, + 976, 976, 976, 643, 691, 691, 691, 691, 976, 686, + 679, 686, 686, 976, 976, 687, 595, 595, 595, 595, + 976, 643, 486, 381, 486, 486, 688, 976, 487, 976, + 488, 489, 490, 490, 490, 490, 380, 381, 380, 380, + 976, 976, 382, 976, 688, 976, 493, 493, 493, 493, + 976, 976, 976, 383, 601, 602, 602, 602, 602, 695, + + 695, 695, 695, 697, 697, 697, 697, 611, 611, 611, + 611, 383, 288, 183, 288, 288, 976, 976, 289, 976, + 290, 291, 498, 498, 498, 498, 288, 183, 288, 288, + 976, 976, 289, 976, 290, 291, 498, 498, 498, 498, + 500, 501, 501, 501, 501, 976, 976, 976, 649, 611, + 611, 611, 611, 704, 704, 976, 705, 705, 705, 705, + 705, 705, 705, 705, 976, 976, 649, 503, 389, 503, + 503, 976, 976, 504, 976, 505, 506, 507, 507, 507, + 507, 503, 389, 503, 503, 976, 976, 504, 976, 505, + 506, 507, 507, 507, 507, 389, 389, 389, 389, 976, + + 976, 390, 976, 976, 976, 509, 509, 509, 509, 299, + 189, 299, 299, 976, 976, 300, 976, 301, 511, 512, + 512, 512, 512, 299, 189, 299, 299, 976, 976, 300, + 976, 301, 302, 652, 652, 652, 652, 393, 394, 393, + 393, 976, 976, 395, 976, 976, 511, 512, 512, 512, + 512, 976, 976, 976, 653, 617, 617, 617, 617, 976, + 712, 712, 618, 713, 713, 713, 713, 705, 705, 705, + 705, 976, 653, 518, 394, 518, 518, 976, 976, 519, + 618, 520, 521, 522, 522, 522, 522, 389, 389, 389, + 389, 976, 976, 390, 976, 976, 515, 516, 516, 516, + + 516, 976, 726, 726, 655, 727, 727, 727, 727, 976, + 976, 302, 525, 525, 525, 525, 976, 976, 976, 396, + 976, 976, 655, 518, 394, 518, 518, 976, 976, 519, + 976, 520, 521, 522, 522, 522, 522, 396, 527, 528, + 528, 528, 528, 976, 759, 759, 659, 760, 760, 760, + 760, 767, 767, 976, 768, 768, 768, 768, 713, 713, + 713, 713, 976, 976, 659, 530, 398, 530, 530, 976, + 976, 531, 976, 532, 533, 534, 534, 534, 534, 530, + 398, 530, 530, 976, 976, 531, 976, 532, 533, 534, + 534, 534, 534, 398, 398, 398, 398, 976, 976, 399, + + 976, 976, 976, 536, 536, 536, 536, 310, 195, 310, + 310, 976, 976, 311, 976, 312, 538, 539, 539, 539, + 539, 310, 195, 310, 310, 976, 976, 311, 976, 312, + 313, 662, 662, 662, 662, 402, 403, 402, 402, 976, + 976, 404, 976, 976, 538, 539, 539, 539, 539, 976, + 976, 976, 663, 648, 648, 648, 648, 976, 775, 775, + 649, 776, 776, 776, 776, 718, 719, 719, 719, 719, + 663, 545, 403, 545, 545, 976, 976, 546, 649, 547, + 548, 549, 549, 549, 549, 398, 398, 398, 398, 976, + 976, 399, 976, 976, 542, 543, 543, 543, 543, 976, + + 780, 780, 665, 781, 781, 781, 781, 976, 976, 313, + 552, 552, 552, 552, 976, 976, 976, 405, 976, 976, + 665, 545, 403, 545, 545, 976, 976, 546, 976, 547, + 548, 549, 549, 549, 549, 405, 669, 669, 669, 669, + 976, 976, 670, 976, 976, 554, 555, 555, 555, 555, + 976, 976, 976, 671, 658, 658, 658, 658, 976, 788, + 788, 659, 789, 789, 789, 789, 860, 860, 860, 860, + 976, 671, 557, 407, 557, 557, 976, 976, 558, 659, + 559, 560, 561, 561, 561, 561, 557, 407, 557, 557, + 976, 976, 558, 976, 559, 560, 561, 561, 561, 561, + + 407, 407, 407, 407, 976, 976, 408, 976, 976, 976, + 563, 563, 563, 563, 324, 208, 324, 324, 976, 976, + 325, 976, 326, 565, 566, 566, 566, 566, 324, 208, + 324, 324, 976, 976, 325, 976, 326, 327, 677, 677, + 677, 677, 678, 679, 678, 678, 976, 976, 680, 976, + 976, 565, 566, 566, 566, 566, 976, 802, 802, 681, + 803, 803, 803, 803, 835, 835, 976, 836, 836, 836, + 836, 862, 862, 862, 862, 976, 976, 681, 572, 412, + 572, 572, 976, 976, 573, 976, 574, 575, 576, 576, + 576, 576, 674, 674, 674, 674, 976, 976, 683, 976, + + 976, 569, 570, 570, 570, 570, 976, 976, 976, 684, + 689, 689, 689, 689, 976, 843, 843, 690, 844, 844, + 844, 844, 727, 727, 727, 727, 976, 684, 572, 412, + 572, 572, 976, 976, 573, 690, 574, 575, 576, 576, + 576, 576, 411, 412, 411, 411, 976, 976, 413, 976, + 976, 976, 579, 579, 579, 579, 976, 851, 851, 414, + 852, 852, 852, 852, 732, 733, 733, 733, 733, 866, + 866, 866, 866, 868, 868, 868, 868, 414, 335, 218, + 335, 335, 976, 976, 336, 976, 337, 338, 584, 584, + 584, 584, 335, 218, 335, 335, 976, 976, 336, 976, + + 337, 338, 584, 584, 584, 584, 586, 587, 587, 587, + 587, 976, 976, 976, 690, 742, 742, 742, 742, 742, + 742, 742, 742, 748, 749, 749, 749, 749, 872, 872, + 872, 872, 690, 589, 420, 589, 589, 976, 976, 590, + 976, 591, 592, 593, 593, 593, 593, 589, 420, 589, + 589, 976, 976, 590, 976, 591, 592, 593, 593, 593, + 593, 420, 420, 420, 420, 976, 976, 421, 976, 976, + 976, 595, 595, 595, 595, 346, 224, 346, 346, 976, + 976, 347, 976, 348, 597, 598, 598, 598, 598, 346, + 224, 346, 346, 976, 976, 347, 976, 348, 349, 693, + + 693, 693, 693, 424, 425, 424, 424, 976, 976, 426, + 976, 976, 597, 598, 598, 598, 598, 976, 976, 976, + 694, 874, 874, 874, 874, 976, 369, 370, 369, 369, + 976, 976, 371, 758, 758, 758, 758, 976, 694, 604, + 425, 604, 604, 372, 976, 605, 976, 606, 607, 608, + 608, 608, 608, 420, 420, 420, 420, 976, 976, 421, + 976, 372, 601, 602, 602, 602, 602, 976, 976, 976, + 696, 758, 758, 758, 758, 976, 976, 349, 611, 611, + 611, 611, 976, 976, 976, 427, 976, 976, 696, 604, + 425, 604, 604, 976, 976, 605, 976, 606, 607, 608, + + 608, 608, 608, 427, 440, 365, 440, 440, 976, 976, + 441, 976, 442, 443, 619, 619, 619, 619, 976, 976, + 976, 620, 760, 760, 760, 760, 766, 766, 766, 766, + 766, 766, 766, 766, 768, 768, 768, 768, 976, 620, + 440, 365, 440, 440, 976, 976, 441, 706, 707, 443, + 708, 708, 708, 708, 369, 370, 369, 369, 976, 976, + 371, 976, 253, 254, 621, 621, 621, 621, 976, 976, + 976, 622, 768, 768, 768, 768, 976, 393, 394, 393, + 393, 976, 976, 395, 776, 776, 776, 776, 976, 622, + 251, 153, 251, 251, 396, 976, 252, 709, 710, 254, + + 711, 711, 711, 711, 365, 365, 365, 365, 976, 976, + 366, 976, 396, 976, 623, 623, 623, 623, 976, 976, + 976, 624, 781, 781, 781, 781, 976, 976, 718, 719, + 719, 719, 719, 976, 976, 976, 861, 976, 976, 624, + 455, 370, 455, 455, 976, 976, 456, 976, 457, 458, + 625, 625, 625, 625, 861, 976, 976, 626, 781, 781, + 781, 781, 976, 402, 403, 402, 402, 976, 976, 404, + 789, 789, 789, 789, 976, 626, 455, 370, 455, 455, + 405, 976, 456, 714, 715, 458, 716, 716, 716, 716, + 628, 628, 628, 628, 976, 976, 629, 976, 405, 976, + + 627, 627, 627, 627, 976, 976, 976, 630, 794, 795, + 795, 795, 795, 878, 878, 878, 878, 880, 880, 880, + 880, 803, 803, 803, 803, 630, 628, 628, 628, 628, + 976, 976, 629, 976, 717, 718, 719, 719, 719, 719, + 721, 628, 721, 721, 976, 976, 722, 976, 723, 724, + 725, 725, 725, 725, 632, 633, 632, 632, 976, 976, + 634, 976, 473, 474, 631, 631, 631, 631, 976, 976, + 976, 635, 808, 809, 809, 809, 809, 884, 884, 884, + 884, 886, 886, 886, 886, 818, 818, 818, 818, 635, + 632, 633, 632, 632, 976, 976, 634, 976, 728, 729, + + 730, 730, 730, 730, 633, 633, 633, 633, 976, 976, + 642, 976, 731, 732, 733, 733, 733, 733, 735, 633, + 735, 735, 976, 976, 736, 976, 737, 738, 739, 739, + 739, 739, 471, 376, 471, 471, 976, 976, 472, 740, + 741, 474, 742, 742, 742, 742, 637, 638, 637, 637, + 976, 976, 639, 976, 277, 278, 636, 636, 636, 636, + 976, 976, 976, 640, 818, 818, 818, 818, 824, 825, + 825, 825, 825, 890, 890, 890, 890, 892, 892, 892, + 892, 640, 637, 638, 637, 637, 976, 976, 639, 976, + 743, 744, 745, 745, 745, 745, 638, 638, 638, 638, + + 976, 976, 746, 976, 747, 748, 749, 749, 749, 749, + 751, 638, 751, 751, 976, 976, 752, 976, 753, 754, + 755, 755, 755, 755, 275, 169, 275, 275, 976, 976, + 276, 756, 757, 278, 758, 758, 758, 758, 633, 633, + 633, 633, 976, 976, 642, 976, 976, 976, 641, 641, + 641, 641, 976, 976, 976, 643, 834, 834, 834, 834, + 834, 834, 834, 834, 836, 836, 836, 836, 842, 842, + 842, 842, 976, 643, 735, 633, 735, 735, 976, 976, + 736, 976, 737, 738, 739, 739, 739, 739, 645, 638, + 645, 645, 976, 976, 646, 976, 488, 489, 644, 644, + + 644, 644, 976, 976, 976, 647, 842, 842, 842, 842, + 844, 844, 844, 844, 844, 844, 844, 844, 852, 852, + 852, 852, 976, 647, 645, 638, 645, 645, 976, 976, + 646, 976, 761, 762, 763, 763, 763, 763, 751, 638, + 751, 751, 976, 976, 752, 976, 753, 754, 755, 755, + 755, 755, 486, 381, 486, 486, 976, 976, 487, 764, + 765, 489, 766, 766, 766, 766, 503, 389, 503, 503, + 976, 976, 504, 976, 505, 506, 650, 650, 650, 650, + 976, 899, 899, 651, 900, 900, 900, 900, 907, 907, + 976, 908, 908, 908, 908, 560, 882, 882, 882, 882, + + 976, 651, 503, 389, 503, 503, 976, 976, 504, 769, + 770, 506, 771, 771, 771, 771, 393, 394, 393, 393, + 976, 976, 395, 976, 301, 302, 652, 652, 652, 652, + 976, 915, 915, 653, 916, 916, 916, 916, 976, 424, + 425, 424, 424, 923, 923, 426, 924, 924, 924, 924, + 976, 653, 299, 189, 299, 299, 427, 976, 300, 772, + 773, 302, 774, 774, 774, 774, 389, 389, 389, 389, + 976, 976, 390, 976, 427, 976, 654, 654, 654, 654, + 976, 925, 926, 655, 927, 927, 927, 927, 976, 976, + 474, 742, 742, 742, 742, 976, 976, 976, 635, 976, + + 976, 655, 518, 394, 518, 518, 976, 976, 519, 976, + 520, 521, 656, 656, 656, 656, 635, 928, 929, 657, + 930, 930, 930, 930, 976, 884, 884, 884, 884, 931, + 931, 976, 932, 932, 932, 932, 976, 657, 518, 394, + 518, 518, 976, 976, 519, 777, 778, 521, 779, 779, + 779, 779, 530, 398, 530, 530, 976, 976, 531, 976, + 532, 533, 660, 660, 660, 660, 976, 933, 934, 661, + 935, 935, 935, 935, 327, 888, 888, 888, 888, 936, + 937, 976, 938, 938, 938, 938, 976, 661, 530, 398, + 530, 530, 976, 976, 531, 782, 783, 533, 784, 784, + + 784, 784, 402, 403, 402, 402, 976, 976, 404, 976, + 312, 313, 662, 662, 662, 662, 976, 976, 976, 663, + 976, 890, 890, 890, 890, 939, 939, 976, 940, 940, + 940, 940, 575, 894, 894, 894, 894, 663, 310, 195, + 310, 310, 976, 976, 311, 785, 786, 313, 787, 787, + 787, 787, 398, 398, 398, 398, 976, 976, 399, 976, + 976, 976, 664, 664, 664, 664, 976, 941, 942, 665, + 943, 943, 943, 943, 976, 976, 278, 758, 758, 758, + 758, 976, 976, 976, 383, 976, 976, 665, 545, 403, + 545, 545, 976, 976, 546, 976, 547, 548, 666, 666, + + 666, 666, 383, 944, 945, 667, 946, 946, 946, 946, + 900, 900, 900, 900, 900, 900, 900, 900, 908, 908, + 908, 908, 976, 667, 545, 403, 545, 545, 976, 976, + 546, 790, 791, 548, 792, 792, 792, 792, 669, 669, + 669, 669, 976, 976, 670, 976, 976, 976, 668, 668, + 668, 668, 976, 976, 976, 671, 860, 860, 860, 860, + 976, 976, 976, 861, 916, 916, 916, 916, 924, 924, + 924, 924, 976, 671, 669, 669, 669, 669, 976, 976, + 670, 861, 793, 794, 795, 795, 795, 795, 797, 669, + 797, 797, 976, 976, 798, 976, 799, 800, 801, 801, + + 801, 801, 673, 674, 673, 673, 976, 976, 675, 976, + 559, 560, 672, 672, 672, 672, 976, 976, 976, 676, + 878, 878, 878, 878, 976, 976, 976, 879, 924, 924, + 924, 924, 927, 927, 927, 927, 976, 676, 673, 674, + 673, 673, 976, 976, 675, 879, 804, 805, 806, 806, + 806, 806, 674, 674, 674, 674, 976, 976, 683, 976, + 807, 808, 809, 809, 809, 809, 811, 674, 811, 811, + 976, 976, 812, 976, 813, 814, 815, 815, 815, 815, + 557, 407, 557, 557, 976, 976, 558, 816, 817, 560, + 818, 818, 818, 818, 678, 679, 678, 678, 976, 976, + + 680, 976, 326, 327, 677, 677, 677, 677, 976, 976, + 976, 681, 880, 880, 880, 880, 976, 976, 976, 881, + 927, 927, 927, 927, 930, 930, 930, 930, 976, 681, + 678, 679, 678, 678, 976, 976, 680, 881, 819, 820, + 821, 821, 821, 821, 679, 679, 679, 679, 976, 976, + 822, 976, 823, 824, 825, 825, 825, 825, 827, 679, + 827, 827, 976, 976, 828, 976, 829, 830, 831, 831, + 831, 831, 324, 208, 324, 324, 976, 976, 325, 832, + 833, 327, 834, 834, 834, 834, 674, 674, 674, 674, + 976, 976, 683, 976, 976, 976, 682, 682, 682, 682, + + 976, 976, 976, 684, 886, 886, 886, 886, 976, 976, + 976, 887, 930, 930, 930, 930, 932, 932, 932, 932, + 976, 684, 811, 674, 811, 811, 976, 976, 812, 887, + 813, 814, 815, 815, 815, 815, 686, 679, 686, 686, + 976, 976, 687, 976, 574, 575, 685, 685, 685, 685, + 976, 976, 976, 688, 892, 892, 892, 892, 976, 976, + 976, 893, 935, 935, 935, 935, 935, 935, 935, 935, + 976, 688, 686, 679, 686, 686, 976, 976, 687, 893, + 837, 838, 839, 839, 839, 839, 827, 679, 827, 827, + 976, 976, 828, 976, 829, 830, 831, 831, 831, 831, + + 572, 412, 572, 572, 976, 976, 573, 840, 841, 575, + 842, 842, 842, 842, 589, 420, 589, 589, 976, 976, + 590, 976, 591, 592, 691, 691, 691, 691, 976, 976, + 976, 692, 938, 938, 938, 938, 938, 938, 938, 938, + 940, 940, 940, 940, 940, 940, 940, 940, 976, 692, + 589, 420, 589, 589, 976, 976, 590, 845, 846, 592, + 847, 847, 847, 847, 424, 425, 424, 424, 976, 976, + 426, 976, 348, 349, 693, 693, 693, 693, 976, 976, + 976, 694, 943, 943, 943, 943, 943, 943, 943, 943, + 946, 946, 946, 946, 946, 946, 946, 946, 976, 694, + + 346, 224, 346, 346, 976, 976, 347, 848, 849, 349, + 850, 850, 850, 850, 420, 420, 420, 420, 976, 976, + 421, 976, 976, 976, 695, 695, 695, 695, 976, 976, + 962, 696, 963, 962, 976, 976, 964, 976, 489, 766, + 766, 766, 766, 976, 976, 976, 647, 976, 976, 696, + 604, 425, 604, 604, 976, 976, 605, 976, 606, 607, + 697, 697, 697, 697, 647, 976, 976, 698, 965, 966, + 967, 965, 976, 962, 968, 962, 962, 976, 962, 964, + 962, 962, 976, 976, 964, 698, 604, 425, 604, 604, + 976, 976, 605, 853, 854, 607, 855, 855, 855, 855, + + 440, 365, 440, 440, 976, 976, 441, 976, 442, 443, + 708, 708, 708, 708, 440, 365, 440, 440, 976, 976, + 441, 976, 442, 443, 708, 708, 708, 708, 251, 153, + 251, 251, 976, 976, 252, 976, 253, 254, 711, 711, + 711, 711, 251, 153, 251, 251, 976, 976, 252, 976, + 253, 254, 711, 711, 711, 711, 365, 365, 365, 365, + 976, 976, 366, 976, 976, 976, 713, 713, 713, 713, + 455, 370, 455, 455, 976, 976, 456, 976, 457, 458, + 716, 716, 716, 716, 455, 370, 455, 455, 976, 976, + 456, 976, 457, 458, 716, 716, 716, 716, 721, 628, + + 721, 721, 976, 976, 722, 976, 723, 724, 725, 725, + 725, 725, 721, 628, 721, 721, 976, 976, 722, 976, + 723, 724, 725, 725, 725, 725, 628, 628, 628, 628, + 976, 976, 629, 976, 976, 976, 727, 727, 727, 727, + 471, 376, 471, 471, 976, 976, 472, 976, 473, 729, + 730, 730, 730, 730, 471, 376, 471, 471, 976, 976, + 472, 976, 473, 474, 864, 864, 864, 864, 632, 633, + 632, 632, 976, 976, 634, 976, 976, 729, 730, 730, + 730, 730, 976, 976, 976, 865, 794, 795, 795, 795, + 795, 976, 976, 976, 879, 927, 927, 927, 927, 976, + + 976, 976, 881, 865, 628, 628, 628, 628, 976, 976, + 629, 976, 879, 732, 733, 733, 733, 733, 976, 976, + 881, 867, 935, 935, 935, 935, 976, 976, 970, 887, + 970, 970, 976, 976, 971, 976, 976, 976, 976, 867, + 735, 633, 735, 735, 976, 976, 736, 887, 737, 738, + 739, 739, 739, 739, 735, 633, 735, 735, 976, 976, + 736, 976, 737, 738, 739, 739, 739, 739, 275, 169, + 275, 275, 976, 976, 276, 976, 277, 744, 745, 745, + 745, 745, 275, 169, 275, 275, 976, 976, 276, 976, + 277, 278, 870, 870, 870, 870, 637, 638, 637, 637, + + 976, 976, 639, 976, 976, 744, 745, 745, 745, 745, + 976, 976, 976, 871, 943, 943, 943, 943, 976, 976, + 976, 893, 965, 959, 967, 965, 976, 976, 968, 976, + 976, 871, 751, 638, 751, 751, 976, 976, 752, 893, + 753, 754, 755, 755, 755, 755, 633, 633, 633, 633, + 976, 976, 642, 976, 976, 748, 749, 749, 749, 749, + 976, 976, 962, 873, 963, 962, 976, 976, 964, 965, + 966, 967, 965, 976, 970, 968, 970, 970, 976, 976, + 971, 873, 751, 638, 751, 751, 976, 976, 752, 976, + 753, 754, 755, 755, 755, 755, 633, 633, 633, 633, + + 976, 976, 642, 976, 976, 976, 760, 760, 760, 760, + 486, 381, 486, 486, 976, 976, 487, 976, 488, 762, + 763, 763, 763, 763, 486, 381, 486, 486, 976, 976, + 487, 976, 488, 489, 876, 876, 876, 876, 645, 638, + 645, 645, 976, 976, 646, 976, 976, 762, 763, 763, + 763, 763, 976, 976, 976, 877, 973, 959, 974, 973, + 976, 970, 975, 970, 970, 976, 970, 971, 970, 970, + 976, 976, 971, 877, 503, 389, 503, 503, 976, 976, + 504, 976, 505, 506, 771, 771, 771, 771, 503, 389, + 503, 503, 976, 976, 504, 976, 505, 506, 771, 771, + + 771, 771, 299, 189, 299, 299, 976, 976, 300, 976, + 301, 302, 774, 774, 774, 774, 299, 189, 299, 299, + 976, 976, 300, 976, 301, 302, 774, 774, 774, 774, + 389, 389, 389, 389, 976, 976, 390, 976, 976, 976, + 776, 776, 776, 776, 518, 394, 518, 518, 976, 976, + 519, 976, 520, 521, 779, 779, 779, 779, 518, 394, + 518, 518, 976, 976, 519, 976, 520, 521, 779, 779, + 779, 779, 530, 398, 530, 530, 976, 976, 531, 976, + 532, 533, 784, 784, 784, 784, 530, 398, 530, 530, + 976, 976, 531, 976, 532, 533, 784, 784, 784, 784, + + 310, 195, 310, 310, 976, 976, 311, 976, 312, 313, + 787, 787, 787, 787, 310, 195, 310, 310, 976, 976, + 311, 976, 312, 313, 787, 787, 787, 787, 398, 398, + 398, 398, 976, 976, 399, 976, 976, 976, 789, 789, + 789, 789, 545, 403, 545, 545, 976, 976, 546, 976, + 547, 548, 792, 792, 792, 792, 545, 403, 545, 545, + 976, 976, 546, 976, 547, 548, 792, 792, 792, 792, + 797, 669, 797, 797, 976, 976, 798, 976, 799, 800, + 801, 801, 801, 801, 797, 669, 797, 797, 976, 976, + 798, 976, 799, 800, 801, 801, 801, 801, 669, 669, + + 669, 669, 976, 976, 670, 976, 976, 976, 803, 803, + 803, 803, 557, 407, 557, 557, 976, 976, 558, 976, + 559, 805, 806, 806, 806, 806, 557, 407, 557, 557, + 976, 976, 558, 976, 559, 560, 882, 882, 882, 882, + 673, 674, 673, 673, 976, 976, 675, 976, 976, 805, + 806, 806, 806, 806, 976, 976, 976, 883, 560, 818, + 818, 818, 818, 976, 976, 976, 676, 973, 959, 974, + 973, 976, 976, 975, 976, 883, 669, 669, 669, 669, + 976, 976, 670, 976, 676, 808, 809, 809, 809, 809, + 976, 976, 976, 885, 973, 959, 974, 973, 976, 976, + + 975, 973, 966, 974, 973, 976, 976, 975, 976, 976, + 976, 885, 811, 674, 811, 811, 976, 976, 812, 976, + 813, 814, 815, 815, 815, 815, 811, 674, 811, 811, + 976, 976, 812, 976, 813, 814, 815, 815, 815, 815, + 324, 208, 324, 324, 976, 976, 325, 976, 326, 820, + 821, 821, 821, 821, 324, 208, 324, 324, 976, 976, + 325, 976, 326, 327, 888, 888, 888, 888, 678, 679, + 678, 678, 976, 976, 680, 976, 976, 820, 821, 821, + 821, 821, 976, 976, 976, 889, 973, 959, 974, 973, + 976, 976, 975, 976, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 889, 827, 679, 827, 827, 976, 976, + 828, 976, 829, 830, 831, 831, 831, 831, 674, 674, + 674, 674, 976, 976, 683, 976, 976, 824, 825, 825, + 825, 825, 976, 976, 976, 891, 976, 976, 976, 976, + 976, 976, 327, 834, 834, 834, 834, 976, 976, 976, + 414, 976, 976, 891, 827, 679, 827, 827, 976, 976, + 828, 976, 829, 830, 831, 831, 831, 831, 414, 674, + 674, 674, 674, 976, 976, 683, 976, 976, 976, 836, + 836, 836, 836, 572, 412, 572, 572, 976, 976, 573, + 976, 574, 838, 839, 839, 839, 839, 572, 412, 572, + + 572, 976, 976, 573, 976, 574, 575, 894, 894, 894, + 894, 686, 679, 686, 686, 976, 976, 687, 976, 976, + 838, 839, 839, 839, 839, 976, 976, 976, 895, 575, + 842, 842, 842, 842, 976, 976, 976, 688, 976, 976, + 976, 976, 976, 976, 976, 976, 895, 976, 976, 976, + 976, 976, 976, 976, 976, 688, 589, 420, 589, 589, + 976, 976, 590, 976, 591, 592, 847, 847, 847, 847, + 589, 420, 589, 589, 976, 976, 590, 976, 591, 592, + 847, 847, 847, 847, 346, 224, 346, 346, 976, 976, + 347, 976, 348, 349, 850, 850, 850, 850, 346, 224, + + 346, 346, 976, 976, 347, 976, 348, 349, 850, 850, + 850, 850, 420, 420, 420, 420, 976, 976, 421, 976, + 976, 976, 852, 852, 852, 852, 604, 425, 604, 604, + 976, 976, 605, 976, 606, 607, 855, 855, 855, 855, + 604, 425, 604, 604, 976, 976, 605, 976, 606, 607, + 855, 855, 855, 855, 721, 628, 721, 721, 976, 976, + 722, 976, 723, 724, 862, 862, 862, 862, 976, 976, + 976, 863, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 863, + 721, 628, 721, 721, 976, 976, 722, 901, 902, 724, + + 903, 903, 903, 903, 632, 633, 632, 632, 976, 976, + 634, 976, 473, 474, 864, 864, 864, 864, 976, 976, + 976, 865, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 865, + 471, 376, 471, 471, 976, 976, 472, 904, 905, 474, + 906, 906, 906, 906, 628, 628, 628, 628, 976, 976, + 629, 976, 976, 976, 866, 866, 866, 866, 976, 976, + 976, 867, 976, 976, 976, 976, 976, 976, 560, 930, + 930, 930, 930, 976, 976, 976, 676, 976, 976, 867, + 735, 633, 735, 735, 976, 976, 736, 976, 737, 738, + + 868, 868, 868, 868, 676, 976, 976, 869, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 869, 735, 633, 735, 735, + 976, 976, 736, 909, 910, 738, 911, 911, 911, 911, + 637, 638, 637, 637, 976, 976, 639, 976, 277, 278, + 870, 870, 870, 870, 976, 976, 976, 871, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 871, 275, 169, 275, 275, + 976, 976, 276, 912, 913, 278, 914, 914, 914, 914, + 633, 633, 633, 633, 976, 976, 642, 976, 976, 976, + + 872, 872, 872, 872, 976, 976, 976, 873, 976, 976, + 976, 976, 976, 976, 327, 938, 938, 938, 938, 976, + 976, 976, 414, 976, 976, 873, 751, 638, 751, 751, + 976, 976, 752, 976, 753, 754, 874, 874, 874, 874, + 414, 976, 976, 875, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 875, 751, 638, 751, 751, 976, 976, 752, 917, + 918, 754, 919, 919, 919, 919, 645, 638, 645, 645, + 976, 976, 646, 976, 488, 489, 876, 876, 876, 876, + 976, 976, 976, 877, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 877, 486, 381, 486, 486, 976, 976, 487, 920, + 921, 489, 922, 922, 922, 922, 632, 633, 632, 632, + 976, 976, 634, 470, 473, 976, 976, 976, 976, 976, + 976, 976, 976, 635, 575, 946, 946, 946, 946, 976, + 976, 976, 688, 976, 976, 976, 976, 976, 976, 976, + 976, 635, 628, 628, 628, 628, 976, 976, 629, 976, + 688, 976, 908, 908, 908, 908, 637, 638, 637, 637, + 976, 976, 639, 274, 277, 976, 976, 976, 976, 976, + 976, 976, 976, 383, 976, 976, 976, 976, 976, 976, + + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 383, 633, 633, 633, 633, 976, 976, 642, 976, + 976, 976, 916, 916, 916, 916, 645, 638, 645, 645, + 976, 976, 646, 485, 488, 976, 976, 976, 976, 976, + 976, 976, 976, 647, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 647, 669, 669, 669, 669, 976, 976, 670, 976, + 976, 976, 932, 932, 932, 932, 46, 46, 46, 46, + 46, 46, 46, 46, 81, 81, 81, 81, 81, 81, + 81, 81, 109, 109, 109, 109, 109, 109, 109, 109, + + 119, 119, 119, 119, 119, 119, 119, 119, 130, 130, + 976, 130, 130, 130, 130, 130, 131, 131, 976, 131, + 131, 131, 131, 131, 134, 976, 976, 134, 135, 976, + 976, 135, 172, 976, 976, 172, 976, 172, 172, 172, + 173, 173, 976, 173, 173, 173, 173, 173, 211, 976, + 976, 211, 976, 211, 211, 212, 212, 976, 212, 212, + 212, 212, 212, 227, 227, 976, 227, 976, 227, 227, + 227, 230, 230, 976, 230, 230, 230, 230, 230, 250, + 250, 250, 250, 250, 250, 250, 250, 263, 263, 263, + 263, 263, 263, 263, 263, 269, 269, 976, 976, 269, + + 269, 274, 274, 274, 274, 274, 274, 274, 274, 287, + 287, 287, 287, 287, 287, 287, 287, 298, 298, 298, + 298, 298, 298, 298, 298, 309, 309, 309, 309, 309, + 309, 309, 309, 323, 323, 323, 323, 323, 323, 323, + 323, 212, 212, 976, 212, 212, 212, 212, 212, 334, + 334, 334, 334, 334, 334, 334, 334, 345, 345, 345, + 345, 345, 345, 345, 345, 227, 227, 976, 227, 976, + 227, 227, 227, 230, 230, 976, 230, 230, 230, 230, + 230, 131, 131, 976, 131, 131, 131, 131, 131, 134, + 976, 976, 134, 250, 250, 250, 250, 250, 250, 250, + + 250, 263, 263, 263, 263, 263, 263, 263, 263, 269, + 269, 976, 976, 269, 269, 274, 274, 274, 274, 274, + 274, 274, 274, 439, 439, 439, 439, 439, 439, 439, + 439, 454, 454, 454, 454, 454, 454, 454, 454, 470, + 470, 470, 470, 470, 470, 470, 470, 485, 485, 485, + 485, 485, 485, 485, 485, 287, 287, 287, 287, 287, + 287, 287, 287, 502, 502, 502, 502, 502, 502, 502, + 502, 298, 298, 298, 298, 298, 298, 298, 298, 517, + 517, 517, 517, 517, 517, 517, 517, 529, 529, 529, + 529, 529, 529, 529, 529, 309, 309, 309, 309, 309, + + 309, 309, 309, 544, 544, 544, 544, 544, 544, 544, + 544, 556, 556, 556, 556, 556, 556, 556, 556, 323, + 323, 323, 323, 323, 323, 323, 323, 571, 571, 571, + 571, 571, 571, 571, 571, 334, 334, 334, 334, 334, + 334, 334, 334, 588, 588, 588, 588, 588, 588, 588, + 588, 345, 345, 345, 345, 345, 345, 345, 345, 603, + 603, 603, 603, 603, 603, 603, 603, 131, 131, 976, + 131, 131, 131, 131, 131, 134, 976, 976, 134, 439, + 439, 439, 439, 439, 439, 439, 439, 250, 250, 250, + 250, 250, 250, 250, 250, 454, 454, 454, 454, 454, + + 454, 454, 454, 470, 470, 470, 470, 470, 470, 470, + 470, 274, 274, 274, 274, 274, 274, 274, 274, 485, + 485, 485, 485, 485, 485, 485, 485, 287, 287, 287, + 287, 287, 287, 287, 287, 502, 502, 502, 502, 502, + 502, 502, 502, 298, 298, 298, 298, 298, 298, 298, + 298, 517, 517, 517, 517, 517, 517, 517, 517, 529, + 529, 529, 529, 529, 529, 529, 529, 309, 309, 309, + 309, 309, 309, 309, 309, 544, 544, 544, 544, 544, + 544, 544, 544, 556, 556, 556, 556, 556, 556, 556, + 556, 323, 323, 323, 323, 323, 323, 323, 323, 571, + + 571, 571, 571, 571, 571, 571, 571, 334, 334, 334, + 334, 334, 334, 334, 334, 588, 588, 588, 588, 588, + 588, 588, 588, 345, 345, 345, 345, 345, 345, 345, + 345, 603, 603, 603, 603, 603, 603, 603, 603, 131, + 131, 976, 131, 131, 131, 131, 131, 134, 976, 976, + 134, 439, 439, 439, 439, 439, 439, 439, 439, 250, + 250, 250, 250, 250, 250, 250, 250, 454, 454, 454, + 454, 454, 454, 454, 454, 720, 720, 720, 720, 720, + 720, 720, 720, 470, 470, 470, 470, 470, 470, 470, + 470, 734, 734, 734, 734, 734, 734, 734, 734, 274, + + 274, 274, 274, 274, 274, 274, 274, 750, 750, 750, + 750, 750, 750, 750, 750, 485, 485, 485, 485, 485, + 485, 485, 485, 502, 502, 502, 502, 502, 502, 502, + 502, 298, 298, 298, 298, 298, 298, 298, 298, 517, + 517, 517, 517, 517, 517, 517, 517, 529, 529, 529, + 529, 529, 529, 529, 529, 309, 309, 309, 309, 309, + 309, 309, 309, 544, 544, 544, 544, 544, 544, 544, + 544, 796, 796, 796, 796, 796, 796, 796, 796, 556, + 556, 556, 556, 556, 556, 556, 556, 810, 810, 810, + 810, 810, 810, 810, 810, 323, 323, 323, 323, 323, + + 323, 323, 323, 826, 826, 826, 826, 826, 826, 826, + 826, 571, 571, 571, 571, 571, 571, 571, 571, 588, + 588, 588, 588, 588, 588, 588, 588, 345, 345, 345, + 345, 345, 345, 345, 345, 603, 603, 603, 603, 603, + 603, 603, 603, 131, 131, 976, 131, 131, 131, 131, + 131, 134, 976, 976, 134, 439, 439, 439, 439, 439, + 439, 439, 439, 250, 250, 250, 250, 250, 250, 250, + 250, 454, 454, 454, 454, 454, 454, 454, 454, 720, + 720, 720, 720, 720, 720, 720, 720, 470, 470, 470, + 470, 470, 470, 470, 470, 734, 734, 734, 734, 734, + + 734, 734, 734, 274, 274, 274, 274, 274, 274, 274, + 274, 750, 750, 750, 750, 750, 750, 750, 750, 485, + 485, 485, 485, 485, 485, 485, 485, 502, 502, 502, + 502, 502, 502, 502, 502, 298, 298, 298, 298, 298, + 298, 298, 298, 517, 517, 517, 517, 517, 517, 517, + 517, 529, 529, 529, 529, 529, 529, 529, 529, 309, + 309, 309, 309, 309, 309, 309, 309, 544, 544, 544, + 544, 544, 544, 544, 544, 796, 796, 796, 796, 796, + 796, 796, 796, 556, 556, 556, 556, 556, 556, 556, + 556, 810, 810, 810, 810, 810, 810, 810, 810, 323, + + 323, 323, 323, 323, 323, 323, 323, 826, 826, 826, + 826, 826, 826, 826, 826, 571, 571, 571, 571, 571, + 571, 571, 571, 588, 588, 588, 588, 588, 588, 588, + 588, 345, 345, 345, 345, 345, 345, 345, 345, 603, + 603, 603, 603, 603, 603, 603, 603, 131, 131, 976, + 131, 131, 131, 131, 131, 134, 976, 976, 134, 720, + 720, 720, 720, 720, 720, 720, 720, 470, 470, 470, + 470, 470, 470, 470, 470, 734, 734, 734, 734, 734, + 734, 734, 734, 274, 274, 274, 274, 274, 274, 274, + 274, 750, 750, 750, 750, 750, 750, 750, 750, 485, + + 485, 485, 485, 485, 485, 485, 485, 958, 958, 958, + 958, 958, 958, 958, 958, 969, 969, 976, 969, 969, + 969, 969, 969, 972, 972, 972, 972, 972, 972, 972, + 972, 45, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976 + } ; + +static yyconst flex_int16_t yy_chk[8681] = + { 0, + 0, 1, 1, 1, 1, 0, 1039, 1, 2, 2, + 2, 2, 122, 122, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 5, 5, 5, 5, 61, 61, 5, + 9, 9, 9, 9, 9, 9, 66, 66, 66, 66, + 66, 5, 5, 10, 10, 10, 10, 10, 10, 1017, + 5, 6, 6, 6, 6, 229, 229, 6, 11, 11, + + 11, 11, 11, 11, 67, 67, 67, 67, 74, 6, + 6, 12, 12, 12, 12, 12, 12, 1014, 6, 7, + 7, 7, 7, 428, 74, 7, 428, 7, 7, 7, + 7, 7, 7, 69, 69, 69, 69, 69, 7, 13, + 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, + 16, 16, 16, 17, 17, 17, 17, 354, 17, 17, + 18, 18, 18, 18, 1012, 18, 18, 70, 70, 70, + 70, 354, 17, 19, 19, 19, 19, 19, 19, 18, + 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, + + 21, 21, 22, 22, 22, 22, 22, 22, 23, 23, + 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 267, 39, 39, 39, + 39, 25, 39, 39, 40, 40, 40, 40, 49, 40, + 40, 133, 267, 25, 1006, 25, 26, 26, 26, 26, + 26, 26, 47, 856, 47, 47, 856, 26, 47, 39, + 133, 49, 41, 41, 41, 41, 40, 41, 41, 26, + 950, 26, 27, 27, 27, 27, 27, 27, 235, 42, + 42, 42, 42, 27, 42, 42, 230, 230, 71, 71, + 71, 71, 71, 235, 41, 27, 71, 27, 28, 28, + + 28, 28, 28, 28, 72, 72, 72, 72, 950, 28, + 999, 42, 231, 231, 71, 75, 75, 75, 75, 947, + 465, 28, 947, 28, 29, 29, 29, 29, 29, 29, + 77, 77, 77, 77, 994, 29, 465, 43, 43, 43, + 43, 43, 43, 79, 79, 79, 79, 29, 43, 29, + 30, 30, 30, 30, 30, 30, 78, 78, 78, 78, + 78, 30, 952, 44, 44, 44, 44, 44, 44, 86, + 86, 86, 86, 30, 44, 30, 31, 31, 31, 31, + 31, 31, 85, 85, 85, 85, 85, 31, 76, 76, + 76, 76, 83, 949, 83, 83, 432, 83, 83, 31, + + 432, 31, 32, 32, 32, 32, 32, 32, 76, 89, + 89, 89, 89, 32, 87, 87, 87, 87, 87, 948, + 699, 898, 87, 612, 76, 32, 699, 32, 33, 33, + 33, 33, 897, 33, 33, 88, 88, 88, 88, 88, + 87, 91, 91, 91, 91, 91, 612, 33, 92, 92, + 92, 92, 94, 94, 94, 94, 94, 901, 901, 33, + 292, 33, 34, 34, 34, 34, 859, 34, 34, 95, + 95, 95, 95, 100, 100, 100, 100, 100, 292, 902, + 902, 34, 101, 101, 101, 101, 102, 102, 102, 102, + 102, 904, 904, 34, 102, 34, 35, 35, 35, 35, + + 35, 35, 103, 103, 103, 103, 857, 35, 104, 104, + 104, 104, 102, 105, 105, 105, 105, 905, 905, 35, + 703, 35, 36, 36, 36, 36, 36, 36, 104, 107, + 107, 107, 107, 36, 106, 106, 106, 106, 106, 113, + 113, 113, 113, 113, 104, 36, 702, 36, 37, 37, + 37, 37, 37, 37, 339, 111, 700, 111, 111, 37, + 111, 111, 114, 114, 114, 114, 116, 116, 116, 116, + 116, 37, 339, 37, 38, 38, 38, 38, 38, 38, + 117, 117, 117, 117, 616, 38, 111, 120, 111, 120, + 120, 466, 128, 120, 128, 128, 444, 38, 128, 38, + + 68, 68, 68, 68, 459, 615, 68, 466, 953, 68, + 68, 68, 68, 68, 444, 909, 909, 68, 125, 125, + 125, 125, 459, 126, 126, 126, 126, 127, 127, 127, + 127, 150, 150, 150, 150, 68, 73, 73, 73, 73, + 896, 614, 73, 126, 896, 953, 73, 73, 73, 73, + 152, 152, 152, 152, 152, 910, 910, 155, 155, 126, + 155, 155, 155, 155, 912, 912, 73, 156, 156, 156, + 156, 157, 157, 157, 157, 157, 913, 913, 613, 157, + 498, 507, 73, 80, 80, 80, 80, 522, 433, 80, + 917, 917, 80, 80, 80, 80, 80, 157, 498, 507, + + 80, 158, 158, 158, 158, 522, 159, 159, 158, 159, + 159, 159, 159, 164, 164, 164, 164, 429, 80, 90, + 90, 90, 90, 918, 918, 90, 158, 534, 90, 90, + 90, 90, 90, 955, 549, 161, 90, 161, 161, 161, + 161, 163, 163, 163, 163, 534, 362, 163, 166, 166, + 166, 166, 549, 584, 90, 93, 93, 93, 93, 920, + 920, 93, 593, 955, 93, 93, 93, 93, 93, 921, + 921, 584, 93, 168, 168, 168, 168, 168, 171, 171, + 593, 171, 171, 171, 171, 176, 176, 176, 176, 956, + 93, 96, 96, 96, 96, 361, 956, 96, 360, 359, + + 96, 96, 96, 96, 96, 358, 357, 173, 96, 173, + 173, 355, 173, 173, 174, 246, 174, 174, 245, 174, + 174, 180, 180, 180, 180, 608, 96, 108, 108, 108, + 108, 244, 708, 108, 716, 243, 108, 108, 108, 108, + 108, 725, 739, 608, 108, 177, 177, 177, 177, 177, + 708, 242, 716, 177, 178, 178, 178, 178, 240, 725, + 739, 178, 108, 115, 115, 115, 115, 239, 238, 115, + 237, 177, 115, 115, 115, 115, 115, 149, 147, 178, + 115, 179, 179, 146, 179, 179, 179, 179, 182, 182, + 182, 182, 182, 186, 186, 186, 186, 145, 115, 118, + + 118, 118, 118, 144, 143, 118, 141, 140, 118, 118, + 118, 118, 118, 139, 185, 185, 118, 185, 185, 185, + 185, 188, 188, 188, 188, 188, 191, 191, 137, 191, + 191, 191, 191, 136, 118, 153, 153, 153, 153, 132, + 131, 153, 129, 153, 153, 153, 153, 153, 153, 154, + 154, 154, 154, 123, 755, 154, 112, 154, 154, 154, + 154, 154, 154, 162, 162, 162, 162, 97, 84, 162, + 65, 162, 755, 162, 162, 162, 162, 169, 169, 169, + 169, 64, 63, 169, 62, 169, 169, 169, 169, 169, + 169, 170, 170, 170, 170, 60, 59, 170, 58, 170, + + 170, 170, 170, 170, 170, 183, 183, 183, 183, 57, + 56, 183, 55, 183, 183, 183, 183, 183, 183, 184, + 184, 184, 184, 54, 53, 184, 51, 184, 184, 184, + 184, 184, 184, 189, 189, 189, 189, 45, 0, 189, + 0, 189, 189, 189, 189, 189, 189, 190, 190, 190, + 190, 0, 0, 190, 0, 190, 190, 190, 190, 190, + 190, 192, 192, 192, 192, 194, 194, 194, 194, 194, + 195, 195, 195, 195, 0, 0, 195, 0, 195, 195, + 195, 195, 195, 195, 196, 196, 196, 196, 0, 0, + 196, 0, 196, 196, 196, 196, 196, 196, 197, 197, + + 0, 197, 197, 197, 197, 199, 199, 199, 199, 200, + 200, 200, 200, 200, 0, 0, 771, 200, 201, 201, + 201, 201, 779, 202, 202, 201, 202, 202, 202, 202, + 203, 203, 203, 203, 771, 200, 205, 205, 205, 205, + 779, 0, 0, 201, 207, 207, 207, 207, 207, 208, + 208, 208, 208, 0, 0, 208, 0, 208, 208, 208, + 208, 208, 208, 209, 209, 209, 209, 784, 0, 209, + 792, 209, 209, 209, 209, 209, 209, 210, 210, 0, + 210, 210, 210, 210, 212, 784, 212, 212, 792, 212, + 212, 213, 0, 213, 213, 0, 213, 213, 215, 215, + + 215, 215, 217, 217, 217, 217, 217, 220, 220, 0, + 220, 220, 220, 220, 801, 212, 0, 212, 221, 221, + 221, 221, 213, 815, 213, 216, 216, 216, 216, 0, + 0, 216, 801, 0, 216, 216, 216, 216, 216, 0, + 0, 815, 216, 223, 223, 223, 223, 223, 226, 226, + 831, 226, 226, 226, 226, 233, 233, 233, 233, 0, + 216, 218, 218, 218, 218, 0, 0, 218, 831, 218, + 218, 218, 218, 218, 218, 219, 219, 219, 219, 847, + 0, 219, 0, 219, 219, 219, 219, 219, 219, 222, + 222, 222, 222, 0, 0, 222, 0, 847, 222, 222, + + 222, 222, 222, 855, 0, 228, 222, 228, 228, 0, + 0, 228, 247, 247, 247, 247, 247, 248, 248, 248, + 248, 855, 0, 0, 222, 224, 224, 224, 224, 0, + 0, 224, 0, 224, 224, 224, 224, 224, 224, 225, + 225, 225, 225, 0, 0, 225, 0, 225, 225, 225, + 225, 225, 225, 249, 249, 249, 249, 0, 0, 249, + 0, 0, 249, 249, 249, 249, 249, 0, 0, 0, + 249, 254, 254, 254, 254, 256, 256, 256, 256, 258, + 258, 258, 258, 259, 259, 259, 259, 0, 249, 250, + 250, 250, 250, 0, 0, 250, 0, 250, 250, 250, + + 250, 250, 250, 251, 251, 251, 251, 0, 0, 251, + 0, 251, 251, 251, 251, 251, 251, 255, 255, 255, + 255, 0, 0, 255, 260, 260, 260, 260, 262, 262, + 262, 262, 0, 0, 255, 0, 0, 261, 261, 261, + 261, 269, 269, 269, 269, 0, 0, 269, 272, 272, + 272, 272, 255, 257, 257, 257, 257, 261, 0, 257, + 0, 0, 0, 257, 257, 257, 257, 271, 271, 271, + 271, 271, 0, 261, 263, 263, 263, 263, 957, 957, + 263, 0, 263, 957, 263, 263, 263, 263, 264, 264, + 264, 264, 958, 958, 264, 0, 264, 958, 264, 264, + + 264, 264, 273, 273, 273, 273, 0, 0, 273, 0, + 0, 273, 273, 273, 273, 273, 0, 0, 0, 273, + 278, 278, 278, 278, 280, 280, 280, 280, 282, 282, + 282, 282, 283, 283, 283, 283, 0, 273, 274, 274, + 274, 274, 0, 0, 274, 0, 274, 274, 274, 274, + 274, 274, 275, 275, 275, 275, 0, 0, 275, 0, + 275, 275, 275, 275, 275, 275, 279, 279, 279, 279, + 0, 0, 279, 284, 284, 284, 284, 284, 285, 285, + 285, 285, 0, 279, 286, 286, 286, 286, 286, 0, + 0, 0, 286, 291, 291, 291, 291, 293, 293, 293, + + 293, 279, 281, 281, 281, 281, 961, 961, 281, 0, + 286, 961, 281, 281, 281, 281, 287, 287, 287, 287, + 0, 0, 287, 0, 287, 287, 287, 287, 287, 287, + 294, 294, 294, 294, 0, 0, 294, 0, 0, 0, + 294, 294, 294, 294, 295, 295, 295, 295, 295, 296, + 296, 296, 296, 297, 297, 297, 297, 0, 0, 297, + 0, 0, 297, 297, 297, 297, 297, 0, 0, 0, + 297, 302, 302, 302, 302, 0, 303, 303, 303, 303, + 0, 0, 303, 304, 304, 304, 304, 0, 297, 298, + 298, 298, 298, 303, 0, 298, 0, 298, 298, 298, + + 298, 298, 298, 306, 306, 306, 306, 306, 0, 903, + 903, 303, 305, 305, 305, 305, 0, 0, 305, 903, + 0, 0, 305, 305, 305, 305, 307, 307, 307, 307, + 308, 308, 308, 308, 0, 0, 308, 903, 0, 308, + 308, 308, 308, 308, 0, 0, 0, 308, 313, 313, + 313, 313, 0, 314, 314, 314, 314, 0, 0, 314, + 315, 315, 315, 315, 0, 308, 309, 309, 309, 309, + 314, 0, 309, 0, 309, 309, 309, 309, 309, 309, + 317, 317, 317, 317, 318, 318, 318, 318, 314, 316, + 316, 316, 316, 0, 0, 316, 0, 0, 0, 316, + + 316, 316, 316, 320, 320, 320, 320, 320, 321, 321, + 321, 321, 322, 322, 322, 322, 0, 0, 322, 0, + 0, 322, 322, 322, 322, 322, 0, 0, 0, 322, + 327, 327, 327, 327, 0, 328, 328, 328, 328, 0, + 0, 328, 329, 329, 329, 329, 0, 322, 323, 323, + 323, 323, 328, 0, 323, 0, 323, 323, 323, 323, + 323, 323, 331, 331, 331, 331, 331, 0, 911, 911, + 328, 330, 330, 330, 330, 0, 0, 330, 911, 0, + 0, 330, 330, 330, 330, 332, 332, 332, 332, 333, + 333, 333, 333, 333, 0, 0, 911, 333, 338, 338, + + 338, 338, 340, 340, 340, 340, 342, 342, 342, 342, + 342, 343, 343, 343, 343, 333, 334, 334, 334, 334, + 0, 0, 334, 0, 334, 334, 334, 334, 334, 334, + 341, 341, 341, 341, 0, 0, 341, 0, 919, 919, + 341, 341, 341, 341, 344, 344, 344, 344, 919, 0, + 344, 0, 0, 344, 344, 344, 344, 344, 0, 0, + 0, 344, 349, 349, 349, 349, 919, 350, 350, 350, + 350, 0, 0, 350, 351, 351, 351, 351, 0, 344, + 345, 345, 345, 345, 350, 0, 345, 0, 345, 345, + 345, 345, 345, 345, 364, 364, 364, 364, 364, 0, + + 0, 0, 350, 352, 352, 352, 352, 0, 0, 352, + 0, 0, 0, 352, 352, 352, 352, 365, 365, 365, + 365, 0, 0, 365, 0, 365, 365, 365, 365, 365, + 365, 366, 366, 366, 366, 0, 0, 366, 0, 366, + 366, 366, 366, 366, 366, 367, 367, 0, 367, 367, + 367, 367, 368, 368, 368, 368, 0, 0, 368, 0, + 0, 0, 368, 368, 368, 368, 0, 372, 372, 368, + 372, 372, 372, 372, 375, 375, 375, 375, 375, 378, + 378, 0, 378, 378, 378, 378, 0, 368, 369, 369, + 369, 369, 369, 369, 0, 0, 0, 369, 384, 384, + + 384, 384, 0, 385, 385, 384, 385, 385, 385, 385, + 436, 436, 436, 436, 436, 369, 370, 370, 370, 370, + 0, 0, 370, 384, 370, 370, 370, 370, 370, 370, + 371, 371, 371, 371, 0, 0, 371, 0, 371, 371, + 371, 371, 371, 371, 374, 374, 374, 374, 374, 374, + 374, 374, 374, 391, 391, 0, 391, 391, 391, 391, + 400, 400, 0, 400, 400, 400, 400, 0, 374, 374, + 374, 376, 376, 376, 376, 0, 0, 376, 0, 376, + 376, 376, 376, 376, 376, 377, 377, 377, 377, 0, + 0, 377, 0, 377, 377, 377, 377, 377, 377, 379, + + 379, 379, 379, 0, 0, 379, 0, 0, 0, 379, + 379, 379, 379, 0, 409, 409, 379, 409, 409, 409, + 409, 415, 415, 415, 415, 0, 416, 416, 415, 416, + 416, 416, 416, 0, 379, 380, 380, 380, 380, 380, + 380, 0, 0, 0, 380, 0, 415, 422, 422, 0, + 422, 422, 422, 422, 437, 437, 437, 437, 443, 443, + 443, 443, 380, 381, 381, 381, 381, 0, 0, 381, + 0, 381, 381, 381, 381, 381, 381, 382, 382, 382, + 382, 0, 0, 382, 0, 382, 382, 382, 382, 382, + 382, 383, 383, 383, 383, 0, 0, 383, 383, 383, + + 0, 383, 383, 383, 383, 0, 0, 0, 383, 445, + 445, 445, 445, 0, 0, 438, 438, 438, 438, 438, + 0, 0, 0, 438, 0, 0, 383, 386, 386, 386, + 386, 0, 0, 386, 0, 386, 386, 386, 386, 386, + 386, 438, 0, 0, 386, 451, 451, 451, 451, 451, + 452, 452, 452, 452, 458, 458, 458, 458, 460, 460, + 460, 460, 386, 387, 387, 387, 387, 0, 0, 387, + 387, 387, 387, 387, 387, 387, 387, 388, 388, 388, + 388, 0, 0, 388, 0, 0, 0, 388, 388, 388, + 388, 0, 0, 0, 388, 461, 461, 461, 461, 467, + + 467, 467, 467, 467, 468, 468, 468, 468, 474, 474, + 474, 474, 388, 389, 389, 389, 389, 0, 0, 389, + 0, 389, 389, 389, 389, 389, 389, 390, 390, 390, + 390, 0, 0, 390, 0, 390, 390, 390, 390, 390, + 390, 392, 392, 392, 392, 0, 0, 392, 0, 392, + 392, 392, 392, 392, 392, 0, 0, 0, 392, 476, + 476, 476, 476, 482, 482, 482, 482, 482, 483, 483, + 483, 483, 489, 489, 489, 489, 392, 393, 393, 393, + 393, 0, 0, 393, 0, 393, 393, 393, 393, 393, + 393, 394, 394, 394, 394, 0, 0, 394, 0, 394, + + 394, 394, 394, 394, 394, 395, 395, 395, 395, 0, + 0, 395, 0, 395, 395, 395, 395, 395, 395, 396, + 396, 396, 396, 0, 0, 396, 396, 396, 396, 396, + 396, 396, 396, 397, 397, 397, 397, 0, 0, 397, + 0, 0, 0, 397, 397, 397, 397, 0, 0, 0, + 397, 491, 491, 491, 491, 492, 492, 492, 492, 494, + 494, 494, 494, 495, 495, 495, 495, 0, 397, 398, + 398, 398, 398, 0, 0, 398, 0, 398, 398, 398, + 398, 398, 398, 399, 399, 399, 399, 0, 0, 399, + 0, 399, 399, 399, 399, 399, 399, 401, 401, 401, + + 401, 0, 0, 401, 0, 401, 401, 401, 401, 401, + 401, 0, 0, 0, 401, 499, 499, 499, 499, 499, + 500, 500, 500, 500, 506, 506, 506, 506, 508, 508, + 508, 508, 401, 402, 402, 402, 402, 0, 0, 402, + 0, 402, 402, 402, 402, 402, 402, 403, 403, 403, + 403, 0, 0, 403, 0, 403, 403, 403, 403, 403, + 403, 404, 404, 404, 404, 0, 0, 404, 0, 404, + 404, 404, 404, 404, 404, 405, 405, 405, 405, 0, + 0, 405, 405, 405, 405, 405, 405, 405, 405, 406, + 406, 406, 406, 0, 0, 406, 0, 0, 0, 406, + + 406, 406, 406, 0, 0, 0, 406, 514, 514, 514, + 514, 514, 515, 515, 515, 515, 521, 521, 521, 521, + 523, 523, 523, 523, 406, 407, 407, 407, 407, 0, + 0, 407, 0, 407, 407, 407, 407, 407, 407, 408, + 408, 408, 408, 0, 0, 408, 0, 408, 408, 408, + 408, 408, 408, 410, 410, 410, 410, 0, 0, 410, + 0, 410, 410, 410, 410, 410, 410, 0, 0, 0, + 410, 524, 524, 524, 524, 526, 526, 526, 526, 526, + 527, 527, 527, 527, 533, 533, 533, 533, 410, 411, + 411, 411, 411, 0, 0, 411, 0, 411, 411, 411, + + 411, 411, 411, 412, 412, 412, 412, 0, 0, 412, + 0, 412, 412, 412, 412, 412, 412, 413, 413, 413, + 413, 0, 0, 413, 0, 413, 413, 413, 413, 413, + 413, 414, 414, 414, 414, 0, 0, 414, 414, 414, + 414, 414, 414, 414, 414, 417, 417, 417, 417, 0, + 0, 417, 0, 417, 417, 417, 417, 417, 417, 0, + 0, 0, 417, 535, 535, 535, 535, 0, 475, 475, + 475, 475, 0, 0, 475, 541, 541, 541, 541, 541, + 417, 418, 418, 418, 418, 475, 0, 418, 418, 418, + 418, 418, 418, 418, 418, 419, 419, 419, 419, 0, + + 0, 419, 0, 475, 0, 419, 419, 419, 419, 0, + 0, 0, 419, 542, 542, 542, 542, 548, 548, 548, + 548, 550, 550, 550, 550, 551, 551, 551, 551, 0, + 419, 420, 420, 420, 420, 0, 0, 420, 0, 420, + 420, 420, 420, 420, 420, 421, 421, 421, 421, 0, + 0, 421, 0, 421, 421, 421, 421, 421, 421, 423, + 423, 423, 423, 0, 0, 423, 0, 423, 423, 423, + 423, 423, 423, 0, 0, 0, 423, 553, 553, 553, + 553, 553, 554, 554, 554, 554, 560, 560, 560, 560, + 562, 562, 562, 562, 423, 424, 424, 424, 424, 0, + + 0, 424, 0, 424, 424, 424, 424, 424, 424, 425, + 425, 425, 425, 0, 0, 425, 0, 425, 425, 425, + 425, 425, 425, 426, 426, 426, 426, 0, 0, 426, + 0, 426, 426, 426, 426, 426, 426, 427, 427, 427, + 427, 0, 0, 427, 427, 427, 427, 427, 427, 427, + 427, 439, 439, 439, 439, 0, 0, 439, 0, 439, + 439, 439, 439, 439, 439, 440, 440, 440, 440, 0, + 0, 440, 0, 440, 440, 440, 440, 440, 440, 446, + 446, 446, 446, 0, 0, 446, 0, 0, 0, 446, + 446, 446, 446, 447, 447, 447, 447, 0, 0, 447, + + 0, 447, 447, 447, 447, 447, 447, 448, 448, 448, + 448, 0, 0, 448, 0, 448, 448, 448, 448, 448, + 448, 449, 449, 449, 449, 0, 0, 449, 0, 0, + 449, 449, 449, 449, 449, 0, 0, 0, 449, 568, + 568, 568, 568, 568, 490, 490, 490, 490, 0, 0, + 490, 569, 569, 569, 569, 0, 449, 450, 450, 450, + 450, 490, 0, 450, 0, 450, 450, 450, 450, 450, + 450, 453, 453, 453, 453, 0, 0, 453, 0, 490, + 453, 453, 453, 453, 453, 0, 0, 0, 453, 575, + 575, 575, 575, 0, 0, 462, 462, 462, 462, 462, + + 0, 0, 0, 462, 0, 0, 453, 454, 454, 454, + 454, 0, 0, 454, 0, 454, 454, 454, 454, 454, + 454, 462, 464, 464, 464, 464, 464, 464, 464, 464, + 464, 464, 464, 464, 464, 464, 464, 464, 464, 464, + 464, 464, 464, 464, 464, 464, 464, 464, 464, 464, + 464, 464, 464, 464, 464, 464, 464, 464, 464, 464, + 464, 464, 464, 464, 464, 464, 464, 464, 464, 464, + 469, 469, 469, 469, 0, 0, 469, 0, 0, 469, + 469, 469, 469, 469, 0, 0, 0, 469, 577, 577, + 577, 577, 578, 578, 578, 578, 580, 580, 580, 580, + + 581, 581, 581, 581, 0, 469, 470, 470, 470, 470, + 0, 0, 470, 0, 470, 470, 470, 470, 470, 470, + 471, 471, 471, 471, 0, 0, 471, 0, 471, 471, + 471, 471, 471, 471, 477, 477, 477, 477, 0, 0, + 477, 0, 0, 0, 477, 477, 477, 477, 478, 478, + 478, 478, 0, 0, 478, 0, 478, 478, 478, 478, + 478, 478, 479, 479, 479, 479, 0, 0, 479, 0, + 479, 479, 479, 479, 479, 479, 480, 480, 480, 480, + 0, 0, 480, 0, 0, 480, 480, 480, 480, 480, + 0, 0, 0, 480, 585, 585, 585, 585, 585, 561, + + 561, 561, 561, 0, 0, 561, 586, 586, 586, 586, + 0, 480, 481, 481, 481, 481, 561, 0, 481, 0, + 481, 481, 481, 481, 481, 481, 484, 484, 484, 484, + 0, 0, 484, 0, 561, 484, 484, 484, 484, 484, + 0, 0, 0, 484, 592, 592, 592, 592, 0, 576, + 576, 576, 576, 0, 0, 576, 594, 594, 594, 594, + 0, 484, 485, 485, 485, 485, 576, 0, 485, 0, + 485, 485, 485, 485, 485, 485, 493, 493, 493, 493, + 0, 0, 493, 0, 576, 0, 493, 493, 493, 493, + 0, 0, 0, 493, 600, 600, 600, 600, 600, 601, + + 601, 601, 601, 607, 607, 607, 607, 609, 609, 609, + 609, 493, 496, 496, 496, 496, 0, 0, 496, 0, + 496, 496, 496, 496, 496, 496, 497, 497, 497, 497, + 0, 0, 497, 0, 497, 497, 497, 497, 497, 497, + 501, 501, 501, 501, 501, 0, 0, 0, 501, 610, + 610, 610, 610, 618, 618, 0, 618, 618, 618, 618, + 704, 704, 704, 704, 0, 0, 501, 502, 502, 502, + 502, 0, 0, 502, 0, 502, 502, 502, 502, 502, + 502, 503, 503, 503, 503, 0, 0, 503, 0, 503, + 503, 503, 503, 503, 503, 509, 509, 509, 509, 0, + + 0, 509, 0, 0, 0, 509, 509, 509, 509, 510, + 510, 510, 510, 0, 0, 510, 0, 510, 510, 510, + 510, 510, 510, 511, 511, 511, 511, 0, 0, 511, + 0, 511, 511, 511, 511, 511, 511, 512, 512, 512, + 512, 0, 0, 512, 0, 0, 512, 512, 512, 512, + 512, 0, 0, 0, 512, 617, 617, 617, 617, 0, + 624, 624, 617, 624, 624, 624, 624, 705, 705, 705, + 705, 0, 512, 513, 513, 513, 513, 0, 0, 513, + 617, 513, 513, 513, 513, 513, 513, 516, 516, 516, + 516, 0, 0, 516, 0, 0, 516, 516, 516, 516, + + 516, 0, 630, 630, 516, 630, 630, 630, 630, 0, + 0, 525, 525, 525, 525, 525, 0, 0, 0, 525, + 0, 0, 516, 517, 517, 517, 517, 0, 0, 517, + 0, 517, 517, 517, 517, 517, 517, 525, 528, 528, + 528, 528, 528, 0, 643, 643, 528, 643, 643, 643, + 643, 649, 649, 0, 649, 649, 649, 649, 712, 712, + 712, 712, 0, 0, 528, 529, 529, 529, 529, 0, + 0, 529, 0, 529, 529, 529, 529, 529, 529, 530, + 530, 530, 530, 0, 0, 530, 0, 530, 530, 530, + 530, 530, 530, 536, 536, 536, 536, 0, 0, 536, + + 0, 0, 0, 536, 536, 536, 536, 537, 537, 537, + 537, 0, 0, 537, 0, 537, 537, 537, 537, 537, + 537, 538, 538, 538, 538, 0, 0, 538, 0, 538, + 538, 538, 538, 538, 538, 539, 539, 539, 539, 0, + 0, 539, 0, 0, 539, 539, 539, 539, 539, 0, + 0, 0, 539, 648, 648, 648, 648, 0, 655, 655, + 648, 655, 655, 655, 655, 717, 717, 717, 717, 717, + 539, 540, 540, 540, 540, 0, 0, 540, 648, 540, + 540, 540, 540, 540, 540, 543, 543, 543, 543, 0, + 0, 543, 0, 0, 543, 543, 543, 543, 543, 0, + + 659, 659, 543, 659, 659, 659, 659, 0, 0, 552, + 552, 552, 552, 552, 0, 0, 0, 552, 0, 0, + 543, 544, 544, 544, 544, 0, 0, 544, 0, 544, + 544, 544, 544, 544, 544, 552, 555, 555, 555, 555, + 0, 0, 555, 0, 0, 555, 555, 555, 555, 555, + 0, 0, 0, 555, 658, 658, 658, 658, 0, 665, + 665, 658, 665, 665, 665, 665, 718, 718, 718, 718, + 0, 555, 556, 556, 556, 556, 0, 0, 556, 658, + 556, 556, 556, 556, 556, 556, 557, 557, 557, 557, + 0, 0, 557, 0, 557, 557, 557, 557, 557, 557, + + 563, 563, 563, 563, 0, 0, 563, 0, 0, 0, + 563, 563, 563, 563, 564, 564, 564, 564, 0, 0, + 564, 0, 564, 564, 564, 564, 564, 564, 565, 565, + 565, 565, 0, 0, 565, 0, 565, 565, 565, 565, + 565, 565, 566, 566, 566, 566, 0, 0, 566, 0, + 0, 566, 566, 566, 566, 566, 0, 671, 671, 566, + 671, 671, 671, 671, 684, 684, 0, 684, 684, 684, + 684, 724, 724, 724, 724, 0, 0, 566, 567, 567, + 567, 567, 0, 0, 567, 0, 567, 567, 567, 567, + 567, 567, 570, 570, 570, 570, 0, 0, 570, 0, + + 0, 570, 570, 570, 570, 570, 0, 0, 0, 570, + 689, 689, 689, 689, 0, 690, 690, 689, 690, 690, + 690, 690, 726, 726, 726, 726, 0, 570, 571, 571, + 571, 571, 0, 0, 571, 689, 571, 571, 571, 571, + 571, 571, 579, 579, 579, 579, 0, 0, 579, 0, + 0, 0, 579, 579, 579, 579, 0, 696, 696, 579, + 696, 696, 696, 696, 731, 731, 731, 731, 731, 732, + 732, 732, 732, 738, 738, 738, 738, 579, 582, 582, + 582, 582, 0, 0, 582, 0, 582, 582, 582, 582, + 582, 582, 583, 583, 583, 583, 0, 0, 583, 0, + + 583, 583, 583, 583, 583, 583, 587, 587, 587, 587, + 587, 0, 0, 0, 587, 740, 740, 740, 740, 741, + 741, 741, 741, 747, 747, 747, 747, 747, 748, 748, + 748, 748, 587, 588, 588, 588, 588, 0, 0, 588, + 0, 588, 588, 588, 588, 588, 588, 589, 589, 589, + 589, 0, 0, 589, 0, 589, 589, 589, 589, 589, + 589, 595, 595, 595, 595, 0, 0, 595, 0, 0, + 0, 595, 595, 595, 595, 596, 596, 596, 596, 0, + 0, 596, 0, 596, 596, 596, 596, 596, 596, 597, + 597, 597, 597, 0, 0, 597, 0, 597, 597, 597, + + 597, 597, 597, 598, 598, 598, 598, 0, 0, 598, + 0, 0, 598, 598, 598, 598, 598, 0, 0, 0, + 598, 754, 754, 754, 754, 0, 711, 711, 711, 711, + 0, 0, 711, 756, 756, 756, 756, 0, 598, 599, + 599, 599, 599, 711, 0, 599, 0, 599, 599, 599, + 599, 599, 599, 602, 602, 602, 602, 0, 0, 602, + 0, 711, 602, 602, 602, 602, 602, 0, 0, 0, + 602, 757, 757, 757, 757, 0, 0, 611, 611, 611, + 611, 611, 0, 0, 0, 611, 0, 0, 602, 603, + 603, 603, 603, 0, 0, 603, 0, 603, 603, 603, + + 603, 603, 603, 611, 619, 619, 619, 619, 0, 0, + 619, 0, 619, 619, 619, 619, 619, 619, 0, 0, + 0, 619, 759, 759, 759, 759, 764, 764, 764, 764, + 765, 765, 765, 765, 767, 767, 767, 767, 0, 619, + 620, 620, 620, 620, 0, 0, 620, 620, 620, 620, + 620, 620, 620, 620, 621, 621, 621, 621, 0, 0, + 621, 0, 621, 621, 621, 621, 621, 621, 0, 0, + 0, 621, 768, 768, 768, 768, 0, 774, 774, 774, + 774, 0, 0, 774, 775, 775, 775, 775, 0, 621, + 622, 622, 622, 622, 774, 0, 622, 622, 622, 622, + + 622, 622, 622, 622, 623, 623, 623, 623, 0, 0, + 623, 0, 774, 0, 623, 623, 623, 623, 0, 0, + 0, 623, 780, 780, 780, 780, 0, 0, 719, 719, + 719, 719, 719, 0, 0, 0, 719, 0, 0, 623, + 625, 625, 625, 625, 0, 0, 625, 0, 625, 625, + 625, 625, 625, 625, 719, 0, 0, 625, 781, 781, + 781, 781, 0, 787, 787, 787, 787, 0, 0, 787, + 788, 788, 788, 788, 0, 625, 626, 626, 626, 626, + 787, 0, 626, 626, 626, 626, 626, 626, 626, 626, + 627, 627, 627, 627, 0, 0, 627, 0, 787, 0, + + 627, 627, 627, 627, 0, 0, 0, 627, 793, 793, + 793, 793, 793, 794, 794, 794, 794, 800, 800, 800, + 800, 802, 802, 802, 802, 627, 628, 628, 628, 628, + 0, 0, 628, 0, 628, 628, 628, 628, 628, 628, + 629, 629, 629, 629, 0, 0, 629, 0, 629, 629, + 629, 629, 629, 629, 631, 631, 631, 631, 0, 0, + 631, 0, 631, 631, 631, 631, 631, 631, 0, 0, + 0, 631, 807, 807, 807, 807, 807, 808, 808, 808, + 808, 814, 814, 814, 814, 816, 816, 816, 816, 631, + 632, 632, 632, 632, 0, 0, 632, 0, 632, 632, + + 632, 632, 632, 632, 633, 633, 633, 633, 0, 0, + 633, 0, 633, 633, 633, 633, 633, 633, 634, 634, + 634, 634, 0, 0, 634, 0, 634, 634, 634, 634, + 634, 634, 635, 635, 635, 635, 0, 0, 635, 635, + 635, 635, 635, 635, 635, 635, 636, 636, 636, 636, + 0, 0, 636, 0, 636, 636, 636, 636, 636, 636, + 0, 0, 0, 636, 817, 817, 817, 817, 823, 823, + 823, 823, 823, 824, 824, 824, 824, 830, 830, 830, + 830, 636, 637, 637, 637, 637, 0, 0, 637, 0, + 637, 637, 637, 637, 637, 637, 638, 638, 638, 638, + + 0, 0, 638, 0, 638, 638, 638, 638, 638, 638, + 639, 639, 639, 639, 0, 0, 639, 0, 639, 639, + 639, 639, 639, 639, 640, 640, 640, 640, 0, 0, + 640, 640, 640, 640, 640, 640, 640, 640, 641, 641, + 641, 641, 0, 0, 641, 0, 0, 0, 641, 641, + 641, 641, 0, 0, 0, 641, 832, 832, 832, 832, + 833, 833, 833, 833, 835, 835, 835, 835, 840, 840, + 840, 840, 0, 641, 642, 642, 642, 642, 0, 0, + 642, 0, 642, 642, 642, 642, 642, 642, 644, 644, + 644, 644, 0, 0, 644, 0, 644, 644, 644, 644, + + 644, 644, 0, 0, 0, 644, 841, 841, 841, 841, + 843, 843, 843, 843, 844, 844, 844, 844, 851, 851, + 851, 851, 0, 644, 645, 645, 645, 645, 0, 0, + 645, 0, 645, 645, 645, 645, 645, 645, 646, 646, + 646, 646, 0, 0, 646, 0, 646, 646, 646, 646, + 646, 646, 647, 647, 647, 647, 0, 0, 647, 647, + 647, 647, 647, 647, 647, 647, 650, 650, 650, 650, + 0, 0, 650, 0, 650, 650, 650, 650, 650, 650, + 0, 861, 861, 650, 861, 861, 861, 861, 867, 867, + 0, 867, 867, 867, 867, 882, 882, 882, 882, 882, + + 0, 650, 651, 651, 651, 651, 0, 0, 651, 651, + 651, 651, 651, 651, 651, 651, 652, 652, 652, 652, + 0, 0, 652, 0, 652, 652, 652, 652, 652, 652, + 0, 873, 873, 652, 873, 873, 873, 873, 0, 850, + 850, 850, 850, 879, 879, 850, 879, 879, 879, 879, + 0, 652, 653, 653, 653, 653, 850, 0, 653, 653, + 653, 653, 653, 653, 653, 653, 654, 654, 654, 654, + 0, 0, 654, 0, 850, 0, 654, 654, 654, 654, + 0, 881, 881, 654, 881, 881, 881, 881, 0, 0, + 742, 742, 742, 742, 742, 0, 0, 0, 742, 0, + + 0, 654, 656, 656, 656, 656, 0, 0, 656, 0, + 656, 656, 656, 656, 656, 656, 742, 883, 883, 656, + 883, 883, 883, 883, 884, 884, 884, 884, 884, 885, + 885, 0, 885, 885, 885, 885, 0, 656, 657, 657, + 657, 657, 0, 0, 657, 657, 657, 657, 657, 657, + 657, 657, 660, 660, 660, 660, 0, 0, 660, 0, + 660, 660, 660, 660, 660, 660, 0, 887, 887, 660, + 887, 887, 887, 887, 888, 888, 888, 888, 888, 889, + 889, 0, 889, 889, 889, 889, 0, 660, 661, 661, + 661, 661, 0, 0, 661, 661, 661, 661, 661, 661, + + 661, 661, 662, 662, 662, 662, 0, 0, 662, 0, + 662, 662, 662, 662, 662, 662, 0, 0, 0, 662, + 890, 890, 890, 890, 890, 891, 891, 0, 891, 891, + 891, 891, 894, 894, 894, 894, 894, 662, 663, 663, + 663, 663, 0, 0, 663, 663, 663, 663, 663, 663, + 663, 663, 664, 664, 664, 664, 0, 0, 664, 0, + 0, 0, 664, 664, 664, 664, 0, 893, 893, 664, + 893, 893, 893, 893, 0, 0, 758, 758, 758, 758, + 758, 0, 0, 0, 758, 0, 0, 664, 666, 666, + 666, 666, 0, 0, 666, 0, 666, 666, 666, 666, + + 666, 666, 758, 895, 895, 666, 895, 895, 895, 895, + 899, 899, 899, 899, 900, 900, 900, 900, 907, 907, + 907, 907, 0, 666, 667, 667, 667, 667, 0, 0, + 667, 667, 667, 667, 667, 667, 667, 667, 668, 668, + 668, 668, 0, 0, 668, 0, 0, 0, 668, 668, + 668, 668, 0, 0, 0, 668, 860, 860, 860, 860, + 0, 0, 0, 860, 915, 915, 915, 915, 923, 923, + 923, 923, 0, 668, 669, 669, 669, 669, 0, 0, + 669, 860, 669, 669, 669, 669, 669, 669, 670, 670, + 670, 670, 0, 0, 670, 0, 670, 670, 670, 670, + + 670, 670, 672, 672, 672, 672, 0, 0, 672, 0, + 672, 672, 672, 672, 672, 672, 0, 0, 0, 672, + 878, 878, 878, 878, 0, 0, 0, 878, 924, 924, + 924, 924, 925, 925, 925, 925, 0, 672, 673, 673, + 673, 673, 0, 0, 673, 878, 673, 673, 673, 673, + 673, 673, 674, 674, 674, 674, 0, 0, 674, 0, + 674, 674, 674, 674, 674, 674, 675, 675, 675, 675, + 0, 0, 675, 0, 675, 675, 675, 675, 675, 675, + 676, 676, 676, 676, 0, 0, 676, 676, 676, 676, + 676, 676, 676, 676, 677, 677, 677, 677, 0, 0, + + 677, 0, 677, 677, 677, 677, 677, 677, 0, 0, + 0, 677, 880, 880, 880, 880, 0, 0, 0, 880, + 926, 926, 926, 926, 928, 928, 928, 928, 0, 677, + 678, 678, 678, 678, 0, 0, 678, 880, 678, 678, + 678, 678, 678, 678, 679, 679, 679, 679, 0, 0, + 679, 0, 679, 679, 679, 679, 679, 679, 680, 680, + 680, 680, 0, 0, 680, 0, 680, 680, 680, 680, + 680, 680, 681, 681, 681, 681, 0, 0, 681, 681, + 681, 681, 681, 681, 681, 681, 682, 682, 682, 682, + 0, 0, 682, 0, 0, 0, 682, 682, 682, 682, + + 0, 0, 0, 682, 886, 886, 886, 886, 0, 0, + 0, 886, 929, 929, 929, 929, 931, 931, 931, 931, + 0, 682, 683, 683, 683, 683, 0, 0, 683, 886, + 683, 683, 683, 683, 683, 683, 685, 685, 685, 685, + 0, 0, 685, 0, 685, 685, 685, 685, 685, 685, + 0, 0, 0, 685, 892, 892, 892, 892, 0, 0, + 0, 892, 933, 933, 933, 933, 934, 934, 934, 934, + 0, 685, 686, 686, 686, 686, 0, 0, 686, 892, + 686, 686, 686, 686, 686, 686, 687, 687, 687, 687, + 0, 0, 687, 0, 687, 687, 687, 687, 687, 687, + + 688, 688, 688, 688, 0, 0, 688, 688, 688, 688, + 688, 688, 688, 688, 691, 691, 691, 691, 0, 0, + 691, 0, 691, 691, 691, 691, 691, 691, 0, 0, + 0, 691, 936, 936, 936, 936, 937, 937, 937, 937, + 939, 939, 939, 939, 940, 940, 940, 940, 0, 691, + 692, 692, 692, 692, 0, 0, 692, 692, 692, 692, + 692, 692, 692, 692, 693, 693, 693, 693, 0, 0, + 693, 0, 693, 693, 693, 693, 693, 693, 0, 0, + 0, 693, 941, 941, 941, 941, 942, 942, 942, 942, + 944, 944, 944, 944, 945, 945, 945, 945, 0, 693, + + 694, 694, 694, 694, 0, 0, 694, 694, 694, 694, + 694, 694, 694, 694, 695, 695, 695, 695, 0, 0, + 695, 0, 0, 0, 695, 695, 695, 695, 0, 0, + 959, 695, 959, 959, 0, 0, 959, 0, 766, 766, + 766, 766, 766, 0, 0, 0, 766, 0, 0, 695, + 697, 697, 697, 697, 0, 0, 697, 0, 697, 697, + 697, 697, 697, 697, 766, 0, 0, 697, 960, 960, + 960, 960, 0, 962, 960, 962, 962, 0, 963, 962, + 963, 963, 0, 0, 963, 697, 698, 698, 698, 698, + 0, 0, 698, 698, 698, 698, 698, 698, 698, 698, + + 706, 706, 706, 706, 0, 0, 706, 0, 706, 706, + 706, 706, 706, 706, 707, 707, 707, 707, 0, 0, + 707, 0, 707, 707, 707, 707, 707, 707, 709, 709, + 709, 709, 0, 0, 709, 0, 709, 709, 709, 709, + 709, 709, 710, 710, 710, 710, 0, 0, 710, 0, + 710, 710, 710, 710, 710, 710, 713, 713, 713, 713, + 0, 0, 713, 0, 0, 0, 713, 713, 713, 713, + 714, 714, 714, 714, 0, 0, 714, 0, 714, 714, + 714, 714, 714, 714, 715, 715, 715, 715, 0, 0, + 715, 0, 715, 715, 715, 715, 715, 715, 720, 720, + + 720, 720, 0, 0, 720, 0, 720, 720, 720, 720, + 720, 720, 721, 721, 721, 721, 0, 0, 721, 0, + 721, 721, 721, 721, 721, 721, 727, 727, 727, 727, + 0, 0, 727, 0, 0, 0, 727, 727, 727, 727, + 728, 728, 728, 728, 0, 0, 728, 0, 728, 728, + 728, 728, 728, 728, 729, 729, 729, 729, 0, 0, + 729, 0, 729, 729, 729, 729, 729, 729, 730, 730, + 730, 730, 0, 0, 730, 0, 0, 730, 730, 730, + 730, 730, 0, 0, 0, 730, 795, 795, 795, 795, + 795, 0, 0, 0, 795, 927, 927, 927, 927, 0, + + 0, 0, 927, 730, 733, 733, 733, 733, 0, 0, + 733, 0, 795, 733, 733, 733, 733, 733, 0, 0, + 927, 733, 935, 935, 935, 935, 0, 0, 964, 935, + 964, 964, 0, 0, 964, 0, 0, 0, 0, 733, + 734, 734, 734, 734, 0, 0, 734, 935, 734, 734, + 734, 734, 734, 734, 735, 735, 735, 735, 0, 0, + 735, 0, 735, 735, 735, 735, 735, 735, 743, 743, + 743, 743, 0, 0, 743, 0, 743, 743, 743, 743, + 743, 743, 744, 744, 744, 744, 0, 0, 744, 0, + 744, 744, 744, 744, 744, 744, 745, 745, 745, 745, + + 0, 0, 745, 0, 0, 745, 745, 745, 745, 745, + 0, 0, 0, 745, 943, 943, 943, 943, 0, 0, + 0, 943, 965, 965, 965, 965, 0, 0, 965, 0, + 0, 745, 746, 746, 746, 746, 0, 0, 746, 943, + 746, 746, 746, 746, 746, 746, 749, 749, 749, 749, + 0, 0, 749, 0, 0, 749, 749, 749, 749, 749, + 0, 0, 966, 749, 966, 966, 0, 0, 966, 967, + 967, 967, 967, 0, 969, 967, 969, 969, 0, 0, + 969, 749, 750, 750, 750, 750, 0, 0, 750, 0, + 750, 750, 750, 750, 750, 750, 760, 760, 760, 760, + + 0, 0, 760, 0, 0, 0, 760, 760, 760, 760, + 761, 761, 761, 761, 0, 0, 761, 0, 761, 761, + 761, 761, 761, 761, 762, 762, 762, 762, 0, 0, + 762, 0, 762, 762, 762, 762, 762, 762, 763, 763, + 763, 763, 0, 0, 763, 0, 0, 763, 763, 763, + 763, 763, 0, 0, 0, 763, 968, 968, 968, 968, + 0, 970, 968, 970, 970, 0, 971, 970, 971, 971, + 0, 0, 971, 763, 769, 769, 769, 769, 0, 0, + 769, 0, 769, 769, 769, 769, 769, 769, 770, 770, + 770, 770, 0, 0, 770, 0, 770, 770, 770, 770, + + 770, 770, 772, 772, 772, 772, 0, 0, 772, 0, + 772, 772, 772, 772, 772, 772, 773, 773, 773, 773, + 0, 0, 773, 0, 773, 773, 773, 773, 773, 773, + 776, 776, 776, 776, 0, 0, 776, 0, 0, 0, + 776, 776, 776, 776, 777, 777, 777, 777, 0, 0, + 777, 0, 777, 777, 777, 777, 777, 777, 778, 778, + 778, 778, 0, 0, 778, 0, 778, 778, 778, 778, + 778, 778, 782, 782, 782, 782, 0, 0, 782, 0, + 782, 782, 782, 782, 782, 782, 783, 783, 783, 783, + 0, 0, 783, 0, 783, 783, 783, 783, 783, 783, + + 785, 785, 785, 785, 0, 0, 785, 0, 785, 785, + 785, 785, 785, 785, 786, 786, 786, 786, 0, 0, + 786, 0, 786, 786, 786, 786, 786, 786, 789, 789, + 789, 789, 0, 0, 789, 0, 0, 0, 789, 789, + 789, 789, 790, 790, 790, 790, 0, 0, 790, 0, + 790, 790, 790, 790, 790, 790, 791, 791, 791, 791, + 0, 0, 791, 0, 791, 791, 791, 791, 791, 791, + 796, 796, 796, 796, 0, 0, 796, 0, 796, 796, + 796, 796, 796, 796, 797, 797, 797, 797, 0, 0, + 797, 0, 797, 797, 797, 797, 797, 797, 803, 803, + + 803, 803, 0, 0, 803, 0, 0, 0, 803, 803, + 803, 803, 804, 804, 804, 804, 0, 0, 804, 0, + 804, 804, 804, 804, 804, 804, 805, 805, 805, 805, + 0, 0, 805, 0, 805, 805, 805, 805, 805, 805, + 806, 806, 806, 806, 0, 0, 806, 0, 0, 806, + 806, 806, 806, 806, 0, 0, 0, 806, 818, 818, + 818, 818, 818, 0, 0, 0, 818, 972, 972, 972, + 972, 0, 0, 972, 0, 806, 809, 809, 809, 809, + 0, 0, 809, 0, 818, 809, 809, 809, 809, 809, + 0, 0, 0, 809, 973, 973, 973, 973, 0, 0, + + 973, 974, 974, 974, 974, 0, 0, 974, 0, 0, + 0, 809, 810, 810, 810, 810, 0, 0, 810, 0, + 810, 810, 810, 810, 810, 810, 811, 811, 811, 811, + 0, 0, 811, 0, 811, 811, 811, 811, 811, 811, + 819, 819, 819, 819, 0, 0, 819, 0, 819, 819, + 819, 819, 819, 819, 820, 820, 820, 820, 0, 0, + 820, 0, 820, 820, 820, 820, 820, 820, 821, 821, + 821, 821, 0, 0, 821, 0, 0, 821, 821, 821, + 821, 821, 0, 0, 0, 821, 975, 975, 975, 975, + 0, 0, 975, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 821, 822, 822, 822, 822, 0, 0, + 822, 0, 822, 822, 822, 822, 822, 822, 825, 825, + 825, 825, 0, 0, 825, 0, 0, 825, 825, 825, + 825, 825, 0, 0, 0, 825, 0, 0, 0, 0, + 0, 0, 834, 834, 834, 834, 834, 0, 0, 0, + 834, 0, 0, 825, 826, 826, 826, 826, 0, 0, + 826, 0, 826, 826, 826, 826, 826, 826, 834, 836, + 836, 836, 836, 0, 0, 836, 0, 0, 0, 836, + 836, 836, 836, 837, 837, 837, 837, 0, 0, 837, + 0, 837, 837, 837, 837, 837, 837, 838, 838, 838, + + 838, 0, 0, 838, 0, 838, 838, 838, 838, 838, + 838, 839, 839, 839, 839, 0, 0, 839, 0, 0, + 839, 839, 839, 839, 839, 0, 0, 0, 839, 842, + 842, 842, 842, 842, 0, 0, 0, 842, 0, 0, + 0, 0, 0, 0, 0, 0, 839, 0, 0, 0, + 0, 0, 0, 0, 0, 842, 845, 845, 845, 845, + 0, 0, 845, 0, 845, 845, 845, 845, 845, 845, + 846, 846, 846, 846, 0, 0, 846, 0, 846, 846, + 846, 846, 846, 846, 848, 848, 848, 848, 0, 0, + 848, 0, 848, 848, 848, 848, 848, 848, 849, 849, + + 849, 849, 0, 0, 849, 0, 849, 849, 849, 849, + 849, 849, 852, 852, 852, 852, 0, 0, 852, 0, + 0, 0, 852, 852, 852, 852, 853, 853, 853, 853, + 0, 0, 853, 0, 853, 853, 853, 853, 853, 853, + 854, 854, 854, 854, 0, 0, 854, 0, 854, 854, + 854, 854, 854, 854, 862, 862, 862, 862, 0, 0, + 862, 0, 862, 862, 862, 862, 862, 862, 0, 0, + 0, 862, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 862, + 863, 863, 863, 863, 0, 0, 863, 863, 863, 863, + + 863, 863, 863, 863, 864, 864, 864, 864, 0, 0, + 864, 0, 864, 864, 864, 864, 864, 864, 0, 0, + 0, 864, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 864, + 865, 865, 865, 865, 0, 0, 865, 865, 865, 865, + 865, 865, 865, 865, 866, 866, 866, 866, 0, 0, + 866, 0, 0, 0, 866, 866, 866, 866, 0, 0, + 0, 866, 0, 0, 0, 0, 0, 0, 930, 930, + 930, 930, 930, 0, 0, 0, 930, 0, 0, 866, + 868, 868, 868, 868, 0, 0, 868, 0, 868, 868, + + 868, 868, 868, 868, 930, 0, 0, 868, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 868, 869, 869, 869, 869, + 0, 0, 869, 869, 869, 869, 869, 869, 869, 869, + 870, 870, 870, 870, 0, 0, 870, 0, 870, 870, + 870, 870, 870, 870, 0, 0, 0, 870, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 870, 871, 871, 871, 871, + 0, 0, 871, 871, 871, 871, 871, 871, 871, 871, + 872, 872, 872, 872, 0, 0, 872, 0, 0, 0, + + 872, 872, 872, 872, 0, 0, 0, 872, 0, 0, + 0, 0, 0, 0, 938, 938, 938, 938, 938, 0, + 0, 0, 938, 0, 0, 872, 874, 874, 874, 874, + 0, 0, 874, 0, 874, 874, 874, 874, 874, 874, + 938, 0, 0, 874, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 874, 875, 875, 875, 875, 0, 0, 875, 875, + 875, 875, 875, 875, 875, 875, 876, 876, 876, 876, + 0, 0, 876, 0, 876, 876, 876, 876, 876, 876, + 0, 0, 0, 876, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 876, 877, 877, 877, 877, 0, 0, 877, 877, + 877, 877, 877, 877, 877, 877, 906, 906, 906, 906, + 0, 0, 906, 906, 906, 0, 0, 0, 0, 0, + 0, 0, 0, 906, 946, 946, 946, 946, 946, 0, + 0, 0, 946, 0, 0, 0, 0, 0, 0, 0, + 0, 906, 908, 908, 908, 908, 0, 0, 908, 0, + 946, 0, 908, 908, 908, 908, 914, 914, 914, 914, + 0, 0, 914, 914, 914, 0, 0, 0, 0, 0, + 0, 0, 0, 914, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 914, 916, 916, 916, 916, 0, 0, 916, 0, + 0, 0, 916, 916, 916, 916, 922, 922, 922, 922, + 0, 0, 922, 922, 922, 0, 0, 0, 0, 0, + 0, 0, 0, 922, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 922, 932, 932, 932, 932, 0, 0, 932, 0, + 0, 0, 932, 932, 932, 932, 977, 977, 977, 977, + 977, 977, 977, 977, 978, 978, 978, 978, 978, 978, + 978, 978, 979, 979, 979, 979, 979, 979, 979, 979, + + 980, 980, 980, 980, 980, 980, 980, 980, 981, 981, + 0, 981, 981, 981, 981, 981, 982, 982, 0, 982, + 982, 982, 982, 982, 983, 0, 0, 983, 984, 0, + 0, 984, 985, 0, 0, 985, 0, 985, 985, 985, + 986, 986, 0, 986, 986, 986, 986, 986, 987, 0, + 0, 987, 0, 987, 987, 988, 988, 0, 988, 988, + 988, 988, 988, 989, 989, 0, 989, 0, 989, 989, + 989, 990, 990, 0, 990, 990, 990, 990, 990, 991, + 991, 991, 991, 991, 991, 991, 991, 992, 992, 992, + 992, 992, 992, 992, 992, 993, 993, 0, 0, 993, + + 993, 995, 995, 995, 995, 995, 995, 995, 995, 996, + 996, 996, 996, 996, 996, 996, 996, 997, 997, 997, + 997, 997, 997, 997, 997, 998, 998, 998, 998, 998, + 998, 998, 998, 1000, 1000, 1000, 1000, 1000, 1000, 1000, + 1000, 1001, 1001, 0, 1001, 1001, 1001, 1001, 1001, 1002, + 1002, 1002, 1002, 1002, 1002, 1002, 1002, 1003, 1003, 1003, + 1003, 1003, 1003, 1003, 1003, 1004, 1004, 0, 1004, 0, + 1004, 1004, 1004, 1005, 1005, 0, 1005, 1005, 1005, 1005, + 1005, 1007, 1007, 0, 1007, 1007, 1007, 1007, 1007, 1008, + 0, 0, 1008, 1009, 1009, 1009, 1009, 1009, 1009, 1009, + + 1009, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1010, 1011, + 1011, 0, 0, 1011, 1011, 1013, 1013, 1013, 1013, 1013, + 1013, 1013, 1013, 1015, 1015, 1015, 1015, 1015, 1015, 1015, + 1015, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1016, 1018, + 1018, 1018, 1018, 1018, 1018, 1018, 1018, 1019, 1019, 1019, + 1019, 1019, 1019, 1019, 1019, 1020, 1020, 1020, 1020, 1020, + 1020, 1020, 1020, 1021, 1021, 1021, 1021, 1021, 1021, 1021, + 1021, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1022, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1024, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1025, 1025, 1025, 1025, 1025, + + 1025, 1025, 1025, 1026, 1026, 1026, 1026, 1026, 1026, 1026, + 1026, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1027, 1028, + 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1029, 1029, 1029, + 1029, 1029, 1029, 1029, 1029, 1030, 1030, 1030, 1030, 1030, + 1030, 1030, 1030, 1031, 1031, 1031, 1031, 1031, 1031, 1031, + 1031, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1032, 1033, + 1033, 1033, 1033, 1033, 1033, 1033, 1033, 1034, 1034, 0, + 1034, 1034, 1034, 1034, 1034, 1035, 0, 0, 1035, 1036, + 1036, 1036, 1036, 1036, 1036, 1036, 1036, 1037, 1037, 1037, + 1037, 1037, 1037, 1037, 1037, 1038, 1038, 1038, 1038, 1038, + + 1038, 1038, 1038, 1040, 1040, 1040, 1040, 1040, 1040, 1040, + 1040, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1041, 1042, + 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1043, 1043, 1043, + 1043, 1043, 1043, 1043, 1043, 1044, 1044, 1044, 1044, 1044, + 1044, 1044, 1044, 1045, 1045, 1045, 1045, 1045, 1045, 1045, + 1045, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1046, 1047, + 1047, 1047, 1047, 1047, 1047, 1047, 1047, 1048, 1048, 1048, + 1048, 1048, 1048, 1048, 1048, 1049, 1049, 1049, 1049, 1049, + 1049, 1049, 1049, 1050, 1050, 1050, 1050, 1050, 1050, 1050, + 1050, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1051, 1052, + + 1052, 1052, 1052, 1052, 1052, 1052, 1052, 1053, 1053, 1053, + 1053, 1053, 1053, 1053, 1053, 1054, 1054, 1054, 1054, 1054, + 1054, 1054, 1054, 1055, 1055, 1055, 1055, 1055, 1055, 1055, + 1055, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1056, 1057, + 1057, 0, 1057, 1057, 1057, 1057, 1057, 1058, 0, 0, + 1058, 1059, 1059, 1059, 1059, 1059, 1059, 1059, 1059, 1060, + 1060, 1060, 1060, 1060, 1060, 1060, 1060, 1061, 1061, 1061, + 1061, 1061, 1061, 1061, 1061, 1062, 1062, 1062, 1062, 1062, + 1062, 1062, 1062, 1063, 1063, 1063, 1063, 1063, 1063, 1063, + 1063, 1064, 1064, 1064, 1064, 1064, 1064, 1064, 1064, 1065, + + 1065, 1065, 1065, 1065, 1065, 1065, 1065, 1066, 1066, 1066, + 1066, 1066, 1066, 1066, 1066, 1067, 1067, 1067, 1067, 1067, + 1067, 1067, 1067, 1068, 1068, 1068, 1068, 1068, 1068, 1068, + 1068, 1069, 1069, 1069, 1069, 1069, 1069, 1069, 1069, 1070, + 1070, 1070, 1070, 1070, 1070, 1070, 1070, 1071, 1071, 1071, + 1071, 1071, 1071, 1071, 1071, 1072, 1072, 1072, 1072, 1072, + 1072, 1072, 1072, 1073, 1073, 1073, 1073, 1073, 1073, 1073, + 1073, 1074, 1074, 1074, 1074, 1074, 1074, 1074, 1074, 1075, + 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1076, 1076, 1076, + 1076, 1076, 1076, 1076, 1076, 1077, 1077, 1077, 1077, 1077, + + 1077, 1077, 1077, 1078, 1078, 1078, 1078, 1078, 1078, 1078, + 1078, 1079, 1079, 1079, 1079, 1079, 1079, 1079, 1079, 1080, + 1080, 1080, 1080, 1080, 1080, 1080, 1080, 1081, 1081, 1081, + 1081, 1081, 1081, 1081, 1081, 1082, 1082, 1082, 1082, 1082, + 1082, 1082, 1082, 1083, 1083, 0, 1083, 1083, 1083, 1083, + 1083, 1084, 0, 0, 1084, 1085, 1085, 1085, 1085, 1085, + 1085, 1085, 1085, 1086, 1086, 1086, 1086, 1086, 1086, 1086, + 1086, 1087, 1087, 1087, 1087, 1087, 1087, 1087, 1087, 1088, + 1088, 1088, 1088, 1088, 1088, 1088, 1088, 1089, 1089, 1089, + 1089, 1089, 1089, 1089, 1089, 1090, 1090, 1090, 1090, 1090, + + 1090, 1090, 1090, 1091, 1091, 1091, 1091, 1091, 1091, 1091, + 1091, 1092, 1092, 1092, 1092, 1092, 1092, 1092, 1092, 1093, + 1093, 1093, 1093, 1093, 1093, 1093, 1093, 1094, 1094, 1094, + 1094, 1094, 1094, 1094, 1094, 1095, 1095, 1095, 1095, 1095, + 1095, 1095, 1095, 1096, 1096, 1096, 1096, 1096, 1096, 1096, + 1096, 1097, 1097, 1097, 1097, 1097, 1097, 1097, 1097, 1098, + 1098, 1098, 1098, 1098, 1098, 1098, 1098, 1099, 1099, 1099, + 1099, 1099, 1099, 1099, 1099, 1100, 1100, 1100, 1100, 1100, + 1100, 1100, 1100, 1101, 1101, 1101, 1101, 1101, 1101, 1101, + 1101, 1102, 1102, 1102, 1102, 1102, 1102, 1102, 1102, 1103, + + 1103, 1103, 1103, 1103, 1103, 1103, 1103, 1104, 1104, 1104, + 1104, 1104, 1104, 1104, 1104, 1105, 1105, 1105, 1105, 1105, + 1105, 1105, 1105, 1106, 1106, 1106, 1106, 1106, 1106, 1106, + 1106, 1107, 1107, 1107, 1107, 1107, 1107, 1107, 1107, 1108, + 1108, 1108, 1108, 1108, 1108, 1108, 1108, 1109, 1109, 0, + 1109, 1109, 1109, 1109, 1109, 1110, 0, 0, 1110, 1111, + 1111, 1111, 1111, 1111, 1111, 1111, 1111, 1112, 1112, 1112, + 1112, 1112, 1112, 1112, 1112, 1113, 1113, 1113, 1113, 1113, + 1113, 1113, 1113, 1114, 1114, 1114, 1114, 1114, 1114, 1114, + 1114, 1115, 1115, 1115, 1115, 1115, 1115, 1115, 1115, 1116, + + 1116, 1116, 1116, 1116, 1116, 1116, 1116, 1117, 1117, 1117, + 1117, 1117, 1117, 1117, 1117, 1118, 1118, 0, 1118, 1118, + 1118, 1118, 1118, 1119, 1119, 1119, 1119, 1119, 1119, 1119, + 1119, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976, + 976, 976, 976, 976, 976, 976, 976, 976, 976, 976 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int vrmlyy_flex_debug; +int vrmlyy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *vrmlyytext; +#line 1 "vrmlLexer.lxx" +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlLexer.lxx + * @author drose + * @date 2004-10-01 + */ +/************************************************** + * VRML 2.0 Parser + * Copyright (C) 1996 Silicon Graphics, Inc. + * + * Author(s) : Gavin Bell + * Daniel Woods (first port) + ************************************************** + */ +#line 26 "vrmlLexer.lxx" +#include "pandatoolbase.h" + +#include "vrmlNode.h" +#include "vrmlParser.h" +#include "pnotify.h" +#include "pstrtod.h" + +static int yyinput(void); // declared by flex. +extern "C" int vrmlyywrap(); + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number as we parse the file, so +// that we can report the position of an error. +static int line_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +static char current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static std::istream *input_p = nullptr; + +// This is the name of the vrml file we're parsing. We keep it so we +// can print it out for error messages. +static std::string vrml_filename; + +extern void vrmlyyerror(const std::string &); + + /* The YACC parser sets this to a token to direct the lexer */ + /* in cases where just syntax isn't enough: */ +int expectToken = 0; + + /* True when parsing a multiple-valued field: */ +static int parsing_mf = 0; + + /* These are used when parsing SFImage fields: */ +static int sfImageIntsParsed = 0; +static int sfImageIntsExpected = 0; + +// This is used while scanning a quoted string. +static std::string quoted_string; + +// And this keeps track of the currently-parsing array. +static MFArray *mfarray; + +void +vrml_init_lexer(std::istream &in, const std::string &filename) { + input_p = ∈ + vrml_filename = filename; + line_number = 0; + error_count = 0; + warning_count = 0; +} + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +vrmlyywrap(void) { + return 1; +} + +void +vrmlyyerror(const std::string &msg) { + using std::cerr; + + cerr << "\nError"; + if (!vrml_filename.empty()) { + cerr << " in " << vrml_filename; + } + cerr + << " at line " << line_number << ":\n" + << current_line << "\n"; + + error_count++; +} + +void +vrmlyywarning(const std::string &msg) { + using std::cerr; + + cerr << "\nWarning"; + if (!vrml_filename.empty()) { + cerr << " in " << vrml_filename; + } + cerr + << " at line " << line_number << ":\n" + << current_line << "\n"; + + warning_count++; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + nassertv(input_p != NULL); + if (*input_p) { + input_p->read(buffer, max_size); + result = input_p->gcount(); + if (result >= 0 && result < max_size) { + // Truncate at the end of the read. + buffer[result] = '\0'; + } + + if (line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the current_line array. This + // is because the \n.* rule below, which fills current_line + // normally, doesn't catch the first line. + strncpy(current_line, vrmlyytext, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + + // Truncate it at the newline. + char *end = strchr(current_line, '\n'); + if (end != NULL) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT + +// Define this macro carefully, since different flex versions call it +// with a different type for result. +#define YY_INPUT(buffer, result, max_size) { \ + int int_result = 0; \ + input_chars((buffer), int_result, (max_size)); \ + (result) = int_result; \ +} + +int extract_int() { + return strtol(vrmlyytext, NULL, 0); +} + +double extract_float() { + return patof(vrmlyytext); +} + +void extract_vec(double vec[], int num_elements) { + char *p = vrmlyytext; + for (int i = 0; i < num_elements; i++) { + vec[i] = pstrtod(p, &p); + } +} + +/* Normal state: parsing nodes. The initial start state is used */ +/* only to recognize the VRML header. */ + +/* Start tokens for all of the field types, */ +/* except for MFNode and SFNode, which are almost completely handled */ +/* by the parser: */ + + + +/* Big hairy expression for floating point numbers: */ +/* Ints are decimal or hex (0x##): */ +/* Whitespace. Using this pattern can screw up currentLineNumber, */ +/* so it is only used wherever it is really convenient and it is */ +/* extremely unlikely that the user will put in a carriage return */ +/* (example: between the floats in an SFVec3f) */ +/* And the same pattern without the newline */ +/* Here's a pattern that matches a single newline character. */ +/* Legal characters to start an identifier */ +/* Legal other characters in an identifier */ +/*idRestChar ([^\x00-\x20\x22\x23\x27\x2b-\x2e\x5b-\x5d\x7b\x7d])*/ +/* Allow hyphen (0x2d) in identifiers. */ +#line 2927 "lex.yy.c" + +#define INITIAL 0 +#define NODE 1 +#define SFB 2 +#define SFC 3 +#define SFF 4 +#define SFIMG 5 +#define SFI 6 +#define SFR 7 +#define SFS 8 +#define SFT 9 +#define SFV2 10 +#define SFV3 11 +#define MFC 12 +#define MFF 13 +#define MFI 14 +#define MFR 15 +#define MFS 16 +#define MFV2 17 +#define MFV3 18 +#define IN_SFS 19 +#define IN_MFS 20 +#define IN_SFIMG 21 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals (void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int vrmlyylex_destroy (void ); + +int vrmlyyget_debug (void ); + +void vrmlyyset_debug (int debug_flag ); + +YY_EXTRA_TYPE vrmlyyget_extra (void ); + +void vrmlyyset_extra (YY_EXTRA_TYPE user_defined ); + +FILE *vrmlyyget_in (void ); + +void vrmlyyset_in (FILE * in_str ); + +FILE *vrmlyyget_out (void ); + +void vrmlyyset_out (FILE * out_str ); + +int vrmlyyget_leng (void ); + +char *vrmlyyget_text (void ); + +int vrmlyyget_lineno (void ); + +void vrmlyyset_lineno (int line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int vrmlyywrap (void ); +#else +extern int vrmlyywrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO fwrite( vrmlyytext, vrmlyyleng, 1, vrmlyyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( vrmlyyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( vrmlyyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, vrmlyyin))==0 && ferror(vrmlyyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(vrmlyyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int vrmlyylex (void); + +#define YY_DECL int vrmlyylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after vrmlyytext and vrmlyyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 217 "vrmlLexer.lxx" + + + + /* Switch into a new start state if the parser */ + /* just told us that we've read a field name */ + /* and should expect a field value (or IS) */ + if (expectToken != 0) { + + /* + * Annoying. This big switch is necessary because + * LEX wants to assign particular numbers to start + * tokens, and YACC wants to define all the tokens + * used, too. Sigh. + */ + switch(expectToken) { + case SFBOOL: BEGIN SFB; break; + case SFCOLOR: BEGIN SFC; break; + case SFFLOAT: BEGIN SFF; break; + case SFIMAGE: BEGIN SFIMG; break; + case SFINT32: BEGIN SFI; break; + case SFROTATION: BEGIN SFR; break; + case SFSTRING: BEGIN SFS; break; + case SFTIME: BEGIN SFT; break; + case SFVEC2F: BEGIN SFV2; break; + case SFVEC3F: BEGIN SFV3; break; + case MFCOLOR: BEGIN MFC; break; + case MFFLOAT: BEGIN MFF; break; + case MFINT32: BEGIN MFI; break; + case MFROTATION: BEGIN MFR; break; + case MFSTRING: BEGIN MFS; break; + case MFVEC2F: BEGIN MFV2; break; + case MFVEC3F: BEGIN MFV3; break; + + /* SFNode and MFNode are special. Here the lexer just returns */ + /* "marker tokens" so the parser knows what type of field is */ + /* being parsed; unlike the other fields, parsing of SFNode/MFNode */ + /* field happens in the parser. */ + case MFNODE: expectToken = 0; return MFNODE; + case SFNODE: expectToken = 0; return SFNODE; + + default: vrmlyyerror("ACK: Bad expectToken"); break; + } + } + + + /* This is more complicated than they really need to be because */ + /* I was ambitious and made the whitespace-matching rule aggressive */ +#line 3178 "lex.yy.c" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! vrmlyyin ) + vrmlyyin = stdin; + + if ( ! vrmlyyout ) + vrmlyyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + vrmlyyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + vrmlyy_create_buffer(vrmlyyin,YY_BUF_SIZE ); + } + + vrmlyy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of vrmlyytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 977 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 8632 ); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + if ( yy_act == 0 ) + { /* have to back up */ + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + yy_act = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 264 "vrmlLexer.lxx" +{ + BEGIN NODE; +} + YY_BREAK +/* The lexer is in the NODE state when parsing nodes, either */ +/* top-level nodes in the .wrl file, in a prototype implementation, */ +/* or when parsing the contents of SFNode or MFNode fields. */ +case 2: +YY_RULE_SETUP +#line 271 "vrmlLexer.lxx" +{ return PROTO; } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 272 "vrmlLexer.lxx" +{ return EXTERNPROTO; } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 273 "vrmlLexer.lxx" +{ return DEF; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 274 "vrmlLexer.lxx" +{ return USE; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 275 "vrmlLexer.lxx" +{ return TO; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 276 "vrmlLexer.lxx" +{ return IS; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 277 "vrmlLexer.lxx" +{ return ROUTE; } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 278 "vrmlLexer.lxx" +{ return SFN_NULL; } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 279 "vrmlLexer.lxx" +{ return EVENTIN; } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 280 "vrmlLexer.lxx" +{ return EVENTOUT; } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 281 "vrmlLexer.lxx" +{ return FIELD; } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 282 "vrmlLexer.lxx" +{ return EXPOSEDFIELD; } + YY_BREAK +/* Legal identifiers: */ +case 14: +YY_RULE_SETUP +#line 285 "vrmlLexer.lxx" +{ + vrmlyylval.string = strdup(vrmlyytext); + return IDENTIFIER; +} + YY_BREAK +/* This hopefully won't bitch things too much. It's not legal for + an identifier to begin with a digit, but Form-Z writes out VRML + files that do. So we'll allow it. Hopefully the start states + will keep them sorted out. */ +case 15: +YY_RULE_SETUP +#line 293 "vrmlLexer.lxx" +{ + vrmlyylval.string = strdup(vrmlyytext); + return IDENTIFIER; +} + YY_BREAK +/* All fields may have an IS declaration: */ +case 16: +YY_RULE_SETUP +#line 299 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + yyless(0); +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 305 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + yyless(0); /* put back the IS */ +} + YY_BREAK +/* All MF field types other than MFNode are completely parsed here */ +/* in the lexer, and one token is returned to the parser. They all */ +/* share the same rules for open and closing brackets: */ +case 18: +YY_RULE_SETUP +#line 314 "vrmlLexer.lxx" +{ + if (parsing_mf) vrmlyyerror("Double ["); + parsing_mf = 1; + mfarray = new MFArray; +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 320 "vrmlLexer.lxx" +{ + if (!parsing_mf) vrmlyyerror("Unmatched ]"); + int fieldType = expectToken; + BEGIN NODE; + parsing_mf = 0; + expectToken = 0; + vrmlyylval.fv._mf = mfarray; + return fieldType; +} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 330 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfbool = true; + return SFBOOL; +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 337 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfbool = false; + return SFBOOL; +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 344 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfint32 = extract_int(); + return SFINT32; +} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 351 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + v._sfint32 = extract_int(); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFINT32; + } +} + YY_BREAK +/* All the floating-point types are pretty similar: */ +case 24: +YY_RULE_SETUP +#line 366 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sffloat = extract_float(); + return SFFLOAT; +} + YY_BREAK +case 25: +YY_RULE_SETUP +#line 373 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + v._sffloat = extract_float(); + if (parsing_mf) { + /* Add to array... */ + mfarray->push_back(v); + } else { + /* No open bracket means a single value: */ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFFLOAT; + } +} + YY_BREAK +case 26: +/* rule 26 can match eol */ +YY_RULE_SETUP +#line 389 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 2); + return SFVEC2F; +} + YY_BREAK +case 27: +/* rule 27 can match eol */ +YY_RULE_SETUP +#line 396 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + extract_vec(v._sfvec, 2); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFVEC2F; + } +} + YY_BREAK +case 28: +/* rule 28 can match eol */ +YY_RULE_SETUP +#line 410 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 3); + return SFVEC3F; +} + YY_BREAK +case 29: +/* rule 29 can match eol */ +YY_RULE_SETUP +#line 417 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + extract_vec(v._sfvec, 3); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFVEC3F; + } +} + YY_BREAK +case 30: +/* rule 30 can match eol */ +YY_RULE_SETUP +#line 431 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 4); + return SFROTATION; +} + YY_BREAK +case 31: +/* rule 31 can match eol */ +YY_RULE_SETUP +#line 438 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + extract_vec(v._sfvec, 4); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFROTATION; + } +} + YY_BREAK +case 32: +/* rule 32 can match eol */ +YY_RULE_SETUP +#line 452 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 3); + return SFCOLOR; +} + YY_BREAK +case 33: +/* rule 33 can match eol */ +YY_RULE_SETUP +#line 459 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + extract_vec(v._sfvec, 3); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFCOLOR; + } +} + YY_BREAK +case 34: +YY_RULE_SETUP +#line 473 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sffloat = extract_float(); + return SFTIME; +} + YY_BREAK +/* SFString/MFString */ +case 35: +YY_RULE_SETUP +#line 481 "vrmlLexer.lxx" +{ + BEGIN IN_SFS; + quoted_string = ""; +} + YY_BREAK +case 36: +YY_RULE_SETUP +#line 486 "vrmlLexer.lxx" +{ + BEGIN IN_MFS; + quoted_string = ""; +} + YY_BREAK +/* Anything besides open-quote (or whitespace) is an error: */ +case 37: +YY_RULE_SETUP +#line 492 "vrmlLexer.lxx" +{ + vrmlyyerror("String missing open-quote"); + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfstring = strdup(""); + return SFSTRING; +} + YY_BREAK +/* Expect open-quote, open-bracket, or whitespace: */ +case 38: +YY_RULE_SETUP +#line 501 "vrmlLexer.lxx" +{ + vrmlyyerror("String missing open-quote"); + BEGIN NODE; + expectToken = 0; + return MFSTRING; +} + YY_BREAK +/* Backslashed-quotes are OK: */ +case 39: +YY_RULE_SETUP +#line 509 "vrmlLexer.lxx" +{ + quoted_string += '"'; +} + YY_BREAK +/* Gobble up anything besides quotes and newlines. */ +/* Newlines are legal in strings, but we exclude them here so */ +/* that line number are counted correctly by the catch-all newline */ +/* rule that applies to everything. */ +case 40: +YY_RULE_SETUP +#line 517 "vrmlLexer.lxx" +{ + quoted_string += vrmlyytext; +} + YY_BREAK +/* Quote ends the string: */ +case 41: +YY_RULE_SETUP +#line 522 "vrmlLexer.lxx" +{ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfstring = strdup(quoted_string.c_str()); + return SFSTRING; +} + YY_BREAK +case 42: +YY_RULE_SETUP +#line 529 "vrmlLexer.lxx" +{ + VrmlFieldValue v; + v._sfstring = strdup(quoted_string.c_str()); + if (parsing_mf) { + BEGIN MFS; + mfarray->push_back(v); + quoted_string = ""; + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFSTRING; + } +} + YY_BREAK +/* SFImage: width height numComponents then width*height integers: */ +case 43: +/* rule 43 can match eol */ +YY_RULE_SETUP +#line 546 "vrmlLexer.lxx" +{ int w, h; + sscanf(vrmlyytext, "%d %d", &w, &h); + sfImageIntsExpected = 1+w*h; + sfImageIntsParsed = 0; + BEGIN IN_SFIMG; + } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 552 "vrmlLexer.lxx" +{ ++sfImageIntsParsed; + if (sfImageIntsParsed == sfImageIntsExpected) { + BEGIN NODE; expectToken = 0; return SFIMAGE; + } + } + YY_BREAK +/* Whitespace and catch-all rules apply to all start states: */ +case 45: +YY_RULE_SETUP +#line 559 "vrmlLexer.lxx" +; + YY_BREAK +/* A newline is also whitespace, but we'll keep track of line number */ +/* to report in errors: */ +case 46: +/* rule 46 can match eol */ +YY_RULE_SETUP +#line 563 "vrmlLexer.lxx" +{ + // Save a copy of the line so we can print it out for the benefit of + // the user in case we get an error. + strncpy(current_line, vrmlyytext+1, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + YY_BREAK +/* This catch-all rule catches anything not covered by any of */ +/* the above: */ +case 47: +YY_RULE_SETUP +#line 577 "vrmlLexer.lxx" +{ + return vrmlyytext[0]; +} + YY_BREAK +case 48: +YY_RULE_SETUP +#line 581 "vrmlLexer.lxx" +ECHO; + YY_BREAK +#line 3748 "lex.yy.c" +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(NODE): +case YY_STATE_EOF(SFB): +case YY_STATE_EOF(SFC): +case YY_STATE_EOF(SFF): +case YY_STATE_EOF(SFIMG): +case YY_STATE_EOF(SFI): +case YY_STATE_EOF(SFR): +case YY_STATE_EOF(SFS): +case YY_STATE_EOF(SFT): +case YY_STATE_EOF(SFV2): +case YY_STATE_EOF(SFV3): +case YY_STATE_EOF(MFC): +case YY_STATE_EOF(MFF): +case YY_STATE_EOF(MFI): +case YY_STATE_EOF(MFR): +case YY_STATE_EOF(MFS): +case YY_STATE_EOF(MFV2): +case YY_STATE_EOF(MFV3): +case YY_STATE_EOF(IN_SFS): +case YY_STATE_EOF(IN_MFS): +case YY_STATE_EOF(IN_SFIMG): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed vrmlyyin at a new source and called + * vrmlyylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = vrmlyyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( vrmlyywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * vrmlyytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of vrmlyylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + vrmlyyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + vrmlyyrestart(vrmlyyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) vrmlyyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 977 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + register char *yy_cp = (yy_c_buf_p); + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 977 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 976); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up vrmlyytext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + vrmlyyrestart(vrmlyyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( vrmlyywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve vrmlyytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void vrmlyyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + vrmlyyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + vrmlyy_create_buffer(vrmlyyin,YY_BUF_SIZE ); + } + + vrmlyy_init_buffer(YY_CURRENT_BUFFER,input_file ); + vrmlyy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void vrmlyy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * vrmlyypop_buffer_state(); + * vrmlyypush_buffer_state(new_buffer); + */ + vrmlyyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + vrmlyy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (vrmlyywrap()) processing, but the only time this flag + * is looked at is after vrmlyywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void vrmlyy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + vrmlyyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE vrmlyy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) vrmlyyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) vrmlyyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + vrmlyy_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with vrmlyy_create_buffer() + * + */ + void vrmlyy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + vrmlyyfree((void *) b->yy_ch_buf ); + + vrmlyyfree((void *) b ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a vrmlyyrestart() or at EOF. + */ + static void vrmlyy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + vrmlyy_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then vrmlyy_init_buffer was _probably_ + * called from vrmlyyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void vrmlyy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + vrmlyy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void vrmlyypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + vrmlyyensure_buffer_stack(); + + /* This block is copied from vrmlyy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from vrmlyy_switch_to_buffer. */ + vrmlyy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void vrmlyypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + vrmlyy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + vrmlyy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void vrmlyyensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)vrmlyyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)vrmlyyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE vrmlyy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) vrmlyyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + vrmlyy_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to vrmlyylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * vrmlyy_scan_bytes() instead. + */ +YY_BUFFER_STATE vrmlyy_scan_string (yyconst char * yystr ) +{ + + return vrmlyy_scan_bytes(yystr,strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to vrmlyylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE vrmlyy_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) vrmlyyalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in vrmlyy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = vrmlyy_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in vrmlyy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up vrmlyytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + vrmlyytext[vrmlyyleng] = (yy_hold_char); \ + (yy_c_buf_p) = vrmlyytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + vrmlyyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int vrmlyyget_lineno (void) +{ + + return vrmlyylineno; +} + +/** Get the input stream. + * + */ +FILE *vrmlyyget_in (void) +{ + return vrmlyyin; +} + +/** Get the output stream. + * + */ +FILE *vrmlyyget_out (void) +{ + return vrmlyyout; +} + +/** Get the length of the current token. + * + */ +int vrmlyyget_leng (void) +{ + return vrmlyyleng; +} + +/** Get the current token. + * + */ + +char *vrmlyyget_text (void) +{ + return vrmlyytext; +} + +/** Set the current line number. + * @param line_number + * + */ +void vrmlyyset_lineno (int line_number ) +{ + + vrmlyylineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see vrmlyy_switch_to_buffer + */ +void vrmlyyset_in (FILE * in_str ) +{ + vrmlyyin = in_str ; +} + +void vrmlyyset_out (FILE * out_str ) +{ + vrmlyyout = out_str ; +} + +int vrmlyyget_debug (void) +{ + return vrmlyy_flex_debug; +} + +void vrmlyyset_debug (int bdebug ) +{ + vrmlyy_flex_debug = bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from vrmlyylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = 0; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = (char *) 0; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + vrmlyyin = stdin; + vrmlyyout = stdout; +#else + vrmlyyin = (FILE *) 0; + vrmlyyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * vrmlyylex_init() + */ + return 0; +} + +/* vrmlyylex_destroy is for both reentrant and non-reentrant scanners. */ +int vrmlyylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + vrmlyy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + vrmlyypop_buffer_state(); + } + + /* Destroy the stack itself. */ + vrmlyyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * vrmlyylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *vrmlyyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *vrmlyyrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void vrmlyyfree (void * ptr ) +{ + free( (char *) ptr ); /* see vrmlyyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 581 "vrmlLexer.lxx" diff --git a/pandatool/src/vrml/vrmlLexer.lxx b/pandatool/src/vrml/vrmlLexer.lxx new file mode 100644 index 00000000..6b03b798 --- /dev/null +++ b/pandatool/src/vrml/vrmlLexer.lxx @@ -0,0 +1,581 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlLexer.lxx + * @author drose + * @date 2004-10-01 + */ + +/************************************************** + * VRML 2.0 Parser + * Copyright (C) 1996 Silicon Graphics, Inc. + * + * Author(s) : Gavin Bell + * Daniel Woods (first port) + ************************************************** + */ +%{ +#include "pandatoolbase.h" + +#include "vrmlNode.h" +#include "vrmlParser.h" +#include "pnotify.h" +#include "pstrtod.h" + +static int yyinput(void); // declared by flex. +extern "C" int vrmlyywrap(); + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number as we parse the file, so +// that we can report the position of an error. +static int line_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +static char current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static std::istream *input_p = nullptr; + +// This is the name of the vrml file we're parsing. We keep it so we +// can print it out for error messages. +static std::string vrml_filename; + +extern void vrmlyyerror(const std::string &); + + /* The YACC parser sets this to a token to direct the lexer */ + /* in cases where just syntax isn't enough: */ +int expectToken = 0; + + /* True when parsing a multiple-valued field: */ +static int parsing_mf = 0; + + /* These are used when parsing SFImage fields: */ +static int sfImageIntsParsed = 0; +static int sfImageIntsExpected = 0; + +// This is used while scanning a quoted string. +static std::string quoted_string; + +// And this keeps track of the currently-parsing array. +static MFArray *mfarray; + +void +vrml_init_lexer(std::istream &in, const std::string &filename) { + input_p = ∈ + vrml_filename = filename; + line_number = 0; + error_count = 0; + warning_count = 0; +} + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +vrmlyywrap(void) { + return 1; +} + +void +vrmlyyerror(const std::string &msg) { + using std::cerr; + + cerr << "\nError"; + if (!vrml_filename.empty()) { + cerr << " in " << vrml_filename; + } + cerr + << " at line " << line_number << ":\n" + << current_line << "\n"; + + error_count++; +} + +void +vrmlyywarning(const std::string &msg) { + using std::cerr; + + cerr << "\nWarning"; + if (!vrml_filename.empty()) { + cerr << " in " << vrml_filename; + } + cerr + << " at line " << line_number << ":\n" + << current_line << "\n"; + + warning_count++; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + nassertv(input_p != nullptr); + if (*input_p) { + input_p->read(buffer, max_size); + result = input_p->gcount(); + if (result >= 0 && result < max_size) { + // Truncate at the end of the read. + buffer[result] = '\0'; + } + + if (line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the current_line array. This + // is because the \n.* rule below, which fills current_line + // normally, doesn't catch the first line. + strncpy(current_line, vrmlyytext, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + + // Truncate it at the newline. + char *end = strchr(current_line, '\n'); + if (end != nullptr) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT + +// Define this macro carefully, since different flex versions call it +// with a different type for result. +#define YY_INPUT(buffer, result, max_size) { \ + int int_result = 0; \ + input_chars((buffer), int_result, (max_size)); \ + (result) = int_result; \ +} + +int extract_int() { + return strtol(yytext, nullptr, 0); +} + +double extract_float() { + return patof(yytext); +} + +void extract_vec(double vec[], int num_elements) { + char *p = yytext; + for (int i = 0; i < num_elements; i++) { + vec[i] = pstrtod(p, &p); + } +} + +%} + + /* Normal state: parsing nodes. The initial start state is used */ + /* only to recognize the VRML header. */ +%x NODE + + /* Start tokens for all of the field types, */ + /* except for MFNode and SFNode, which are almost completely handled */ + /* by the parser: */ +%x SFB SFC SFF SFIMG SFI SFR SFS SFT SFV2 SFV3 +%x MFC MFF MFI MFR MFS MFV2 MFV3 +%x IN_SFS IN_MFS IN_SFIMG + + /* Big hairy expression for floating point numbers: */ +float (-?((([0-9]+)|([0-9]*\.[0-9]+))([eE][+\-]?[0-9]+)?)) + + /* Ints are decimal or hex (0x##): */ +int (-?([0-9]+)|(0[xX][0-9a-fA-F]*)) + + /* Whitespace. Using this pattern can screw up currentLineNumber, */ + /* so it is only used wherever it is really convenient and it is */ + /* extremely unlikely that the user will put in a carriage return */ + /* (example: between the floats in an SFVec3f) */ +ws ([ \t\r\n,]|(#.*))+ + /* And the same pattern without the newline */ +wsnnl ([ \t\r,]|(#.*)) + /* Here's a pattern that matches a single newline character. */ +nl ((\n)|(\n\r)|(\r\n)|(\r)) + + /* Legal characters to start an identifier */ +idStartChar ([^\x30-\x39\x00-\x20\x22\x23\x27\x2b-\x2e\x5b-\x5d\x7b\x7d]) + /* Legal other characters in an identifier */ + /*idRestChar ([^\x00-\x20\x22\x23\x27\x2b-\x2e\x5b-\x5d\x7b\x7d])*/ + + /* Allow hyphen (0x2d) in identifiers. */ +idRestChar ([^\x00-\x20\x22\x23\x27\x2b-\x2c\x2e\x5b-\x5d\x7b\x7d]) +%% + +%{ + /* Switch into a new start state if the parser */ + /* just told us that we've read a field name */ + /* and should expect a field value (or IS) */ + if (expectToken != 0) { + + /* + * Annoying. This big switch is necessary because + * LEX wants to assign particular numbers to start + * tokens, and YACC wants to define all the tokens + * used, too. Sigh. + */ + switch(expectToken) { + case SFBOOL: BEGIN SFB; break; + case SFCOLOR: BEGIN SFC; break; + case SFFLOAT: BEGIN SFF; break; + case SFIMAGE: BEGIN SFIMG; break; + case SFINT32: BEGIN SFI; break; + case SFROTATION: BEGIN SFR; break; + case SFSTRING: BEGIN SFS; break; + case SFTIME: BEGIN SFT; break; + case SFVEC2F: BEGIN SFV2; break; + case SFVEC3F: BEGIN SFV3; break; + case MFCOLOR: BEGIN MFC; break; + case MFFLOAT: BEGIN MFF; break; + case MFINT32: BEGIN MFI; break; + case MFROTATION: BEGIN MFR; break; + case MFSTRING: BEGIN MFS; break; + case MFVEC2F: BEGIN MFV2; break; + case MFVEC3F: BEGIN MFV3; break; + + /* SFNode and MFNode are special. Here the lexer just returns */ + /* "marker tokens" so the parser knows what type of field is */ + /* being parsed; unlike the other fields, parsing of SFNode/MFNode */ + /* field happens in the parser. */ + case MFNODE: expectToken = 0; return MFNODE; + case SFNODE: expectToken = 0; return SFNODE; + + default: vrmlyyerror("ACK: Bad expectToken"); break; + } + } +%} + + /* This is more complicated than they really need to be because */ + /* I was ambitious and made the whitespace-matching rule aggressive */ +"#VRML V2.0 utf8".*{nl}{wsnnl}* { + BEGIN NODE; +} + + /* The lexer is in the NODE state when parsing nodes, either */ + /* top-level nodes in the .wrl file, in a prototype implementation, */ + /* or when parsing the contents of SFNode or MFNode fields. */ +PROTO { return PROTO; } +EXTERNPROTO { return EXTERNPROTO; } +DEF { return DEF; } +USE { return USE; } +TO { return TO; } +IS { return IS; } +ROUTE { return ROUTE; } +nullptr { return SFN_NULL; } +eventIn { return EVENTIN; } +eventOut { return EVENTOUT; } +field { return FIELD; } +exposedField { return EXPOSEDFIELD; } + + /* Legal identifiers: */ +{idStartChar}{idRestChar}* { + vrmlyylval.string = strdup(yytext); + return IDENTIFIER; +} + /* This hopefully won't bitch things too much. It's not legal for + an identifier to begin with a digit, but Form-Z writes out VRML + files that do. So we'll allow it. Hopefully the start states + will keep them sorted out. */ +[0-9]{idRestChar}* { + vrmlyylval.string = strdup(yytext); + return IDENTIFIER; +} + + /* All fields may have an IS declaration: */ +IS { + BEGIN NODE; + expectToken = 0; + yyless(0); +} + +IS { + BEGIN NODE; + expectToken = 0; + yyless(0); /* put back the IS */ +} + + /* All MF field types other than MFNode are completely parsed here */ + /* in the lexer, and one token is returned to the parser. They all */ + /* share the same rules for open and closing brackets: */ +\[ { + if (parsing_mf) vrmlyyerror("Double ["); + parsing_mf = 1; + mfarray = new MFArray; +} + +\] { + if (!parsing_mf) vrmlyyerror("Unmatched ]"); + int fieldType = expectToken; + BEGIN NODE; + parsing_mf = 0; + expectToken = 0; + vrmlyylval.fv._mf = mfarray; + return fieldType; +} + +TRUE { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfbool = true; + return SFBOOL; +} + +FALSE { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfbool = false; + return SFBOOL; +} + +{int} { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfint32 = extract_int(); + return SFINT32; +} + +{int} { + VrmlFieldValue v; + v._sfint32 = extract_int(); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFINT32; + } +} + + /* All the floating-point types are pretty similar: */ +{float} { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sffloat = extract_float(); + return SFFLOAT; +} + +{float} { + VrmlFieldValue v; + v._sffloat = extract_float(); + if (parsing_mf) { + /* Add to array... */ + mfarray->push_back(v); + } else { + /* No open bracket means a single value: */ + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFFLOAT; + } +} + +{float}{ws}{float} { + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 2); + return SFVEC2F; +} + +{float}{ws}{float} { + VrmlFieldValue v; + extract_vec(v._sfvec, 2); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFVEC2F; + } +} + +({float}{ws}){2}{float} { + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 3); + return SFVEC3F; +} + +({float}{ws}){2}{float} { + VrmlFieldValue v; + extract_vec(v._sfvec, 3); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFVEC3F; + } +} + +({float}{ws}){3}{float} { + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 4); + return SFROTATION; +} + +({float}{ws}){3}{float} { + VrmlFieldValue v; + extract_vec(v._sfvec, 4); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFROTATION; + } +} + +({float}{ws}){2}{float} { + BEGIN NODE; + expectToken = 0; + extract_vec(vrmlyylval.fv._sfvec, 3); + return SFCOLOR; +} + +({float}{ws}){2}{float} { + VrmlFieldValue v; + extract_vec(v._sfvec, 3); + if (parsing_mf) { + mfarray->push_back(v); + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFCOLOR; + } +} + +{float} { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sffloat = extract_float(); + return SFTIME; +} + + /* SFString/MFString */ +\" { + BEGIN IN_SFS; + quoted_string = ""; +} + +\" { + BEGIN IN_MFS; + quoted_string = ""; +} + + /* Anything besides open-quote (or whitespace) is an error: */ +[^ \"\t\r\,\n]+ { + vrmlyyerror("String missing open-quote"); + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfstring = strdup(""); + return SFSTRING; +} + + /* Expect open-quote, open-bracket, or whitespace: */ +[^ \[\]\"\t\r\,\n]+ { + vrmlyyerror("String missing open-quote"); + BEGIN NODE; + expectToken = 0; + return MFSTRING; +} + + /* Backslashed-quotes are OK: */ +\\\" { + quoted_string += '"'; +} + + /* Gobble up anything besides quotes and newlines. */ + /* Newlines are legal in strings, but we exclude them here so */ + /* that line number are counted correctly by the catch-all newline */ + /* rule that applies to everything. */ +[^\"\n]+ { + quoted_string += yytext; +} + + /* Quote ends the string: */ +\" { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._sfstring = strdup(quoted_string.c_str()); + return SFSTRING; +} + +\" { + VrmlFieldValue v; + v._sfstring = strdup(quoted_string.c_str()); + if (parsing_mf) { + BEGIN MFS; + mfarray->push_back(v); + quoted_string = ""; + } else { + BEGIN NODE; + expectToken = 0; + vrmlyylval.fv._mf = new MFArray; + vrmlyylval.fv._mf->push_back(v); + return MFSTRING; + } +} + + /* SFImage: width height numComponents then width*height integers: */ +{int}{ws}{int} { int w, h; + sscanf(yytext, "%d %d", &w, &h); + sfImageIntsExpected = 1+w*h; + sfImageIntsParsed = 0; + BEGIN IN_SFIMG; + } +{int} { ++sfImageIntsParsed; + if (sfImageIntsParsed == sfImageIntsExpected) { + BEGIN NODE; expectToken = 0; return SFIMAGE; + } + } + + /* Whitespace and catch-all rules apply to all start states: */ +<*>{wsnnl}+ ; + + /* A newline is also whitespace, but we'll keep track of line number */ + /* to report in errors: */ +<*>\n.* { + // Save a copy of the line so we can print it out for the benefit of + // the user in case we get an error. + strncpy(current_line, yytext+1, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + + /* This catch-all rule catches anything not covered by any of */ + /* the above: */ +<*>. { + return yytext[0]; +} + diff --git a/pandatool/src/vrml/vrmlLexerDefs.h b/pandatool/src/vrml/vrmlLexerDefs.h new file mode 100644 index 00000000..5cbdc81b --- /dev/null +++ b/pandatool/src/vrml/vrmlLexerDefs.h @@ -0,0 +1,28 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlLexerDefs.h + * @author drose + * @date 2004-09-30 + */ + +#ifndef VRMLLEXERDEFS_H +#define VRMLLEXERDEFS_H + +#include "pandatoolbase.h" + +void vrml_init_lexer(std::istream &in, const std::string &filename); +int vrml_error_count(); +int vrml_warning_count(); + +void vrmlyyerror(const std::string &msg); +void vrmlyywarning(const std::string &msg); + +int vrmlyylex(); + +#endif diff --git a/pandatool/src/vrml/vrmlNode.cxx b/pandatool/src/vrml/vrmlNode.cxx new file mode 100644 index 00000000..c48ce35e --- /dev/null +++ b/pandatool/src/vrml/vrmlNode.cxx @@ -0,0 +1,80 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlNode.cxx + * @author drose + * @date 1999-06-23 + */ + +#include "vrmlNode.h" +#include "vrmlParser.h" + +#include "indent.h" +#include "pnotify.h" + +VrmlNode:: +VrmlNode(const VrmlNodeType *type) { + _type = type; + _use_count = 0; +} + +VrmlNode:: +~VrmlNode() { +} + + +const VrmlFieldValue &VrmlNode:: +get_value(const char *field_name) const { + Fields::const_iterator fi; + for (fi = _fields.begin(); fi != _fields.end(); ++fi) { + if (strcmp((*fi)._type->name, field_name) == 0) { + return ((*fi)._value); + } + } + + // That field was not defined. Get the default value. + const VrmlNodeType::NameTypeRec *field = _type->hasField(field_name); + if (field != nullptr) { + return field->dflt; + } + + std::cerr << "No such field defined for type " << _type->getName() << ": " + << field_name << "\n"; + exit(1); + // Just to make the compiler happy. + static VrmlFieldValue zero; + return zero; +} + +void VrmlNode:: +output(std::ostream &out, int indent_level) const { + out << _type->getName() << " {\n"; + Fields::const_iterator fi; + for (fi = _fields.begin(); fi != _fields.end(); ++fi) { + indent(out, indent_level + 2) << (*fi)._type->name << " "; + output_value(out, (*fi)._value, (*fi)._type->type, indent_level + 2) << "\n"; + } + indent(out, indent_level) << "}"; +} + + +void Declaration:: +output(std::ostream &out, int indent) const { + VrmlFieldValue v; + v._sfnode = _node; + output_value(out, v, SFNODE, indent); +} + +std::ostream &operator << (std::ostream &out, const VrmlScene &scene) { + VrmlScene::const_iterator si; + for (si = scene.begin(); si != scene.end(); ++si) { + out << (*si) << "\n"; + } + + return out; +} diff --git a/pandatool/src/vrml/vrmlNode.h b/pandatool/src/vrml/vrmlNode.h new file mode 100644 index 00000000..648f477c --- /dev/null +++ b/pandatool/src/vrml/vrmlNode.h @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlNode.h + * @author drose + * @date 1999-06-23 + */ + +#ifndef VRMLNODE_H +#define VRMLNODE_H + +#include "pandatoolbase.h" + +#include "vrmlNodeType.h" +#include "pvector.h" +#include "pmap.h" + +class VrmlNode { +public: + VrmlNode(const VrmlNodeType *type); + ~VrmlNode(); + + const VrmlFieldValue &get_value(const char *field_name) const; + + void output(std::ostream &out, int indent) const; + + class Field { + public: + Field() { } + Field(const VrmlNodeType::NameTypeRec *type, const VrmlFieldValue &value) : + _type(type), _value(value) { } + const VrmlNodeType::NameTypeRec *_type; + VrmlFieldValue _value; + }; + + typedef std::vector Fields; + Fields _fields; + + int _use_count; + + const VrmlNodeType *_type; +}; + +inline std::ostream &operator << (std::ostream &out, const VrmlNode &node) { + node.output(out, 0); + return out; +} + +class Declaration { +public: + SFNodeRef _node; + + void output(std::ostream &out, int indent) const; +}; + +inline std::ostream &operator << (std::ostream &out, const Declaration &dec) { + dec.output(out, 0); + return out; +} + +typedef pvector VrmlScene; + +std::ostream &operator << (std::ostream &out, const VrmlScene &scene); + +#endif diff --git a/pandatool/src/vrml/vrmlNodeType.cxx b/pandatool/src/vrml/vrmlNodeType.cxx new file mode 100644 index 00000000..56652507 --- /dev/null +++ b/pandatool/src/vrml/vrmlNodeType.cxx @@ -0,0 +1,338 @@ +/************************************************** + * VRML 2.0 Parser + * Copyright (C) 1996 Silicon Graphics, Inc. + * + * Author(s) : Gavin Bell + * Daniel Woods (first port) + ************************************************** + */ + +// +// The VrmlNodeType class is responsible for storing information about node +// or prototype types. +// + +#include "vrmlNodeType.h" +#include "vrmlNode.h" +#include "vrmlParser.h" +#include "pnotify.h" +#include "indent.h" + +#include // for sprintf() + +using std::ostream; + + +// +// Static list of node types. +// +plist VrmlNodeType::typeList; + +static ostream & +output_array(ostream &out, const MFArray *mf, + int type, int indent_level, int items_per_row) { + if (mf->empty()) { + out << "[ ]"; + } else { + out << "["; + MFArray::const_iterator mi; + int col = 0; + for (mi = mf->begin(); mi != mf->end(); ++mi) { + if (col == 0) { + out << "\n"; + indent(out, indent_level + 2); + } + output_value(out, (*mi), type, indent_level + 2); + if (++col >= items_per_row) { + col = 0; + } else { + out << " "; + } + } + out << "\n"; + indent(out, indent_level) << "]"; + } + return out; +} + +ostream & +output_value(ostream &out, const VrmlFieldValue &value, int type, + int indent) { + switch (type) { + case SFBOOL: + return out << (value._sfbool ? "TRUE" : "FALSE"); + + case SFFLOAT: + case SFTIME: + return out << value._sffloat; + + case SFINT32: + return out << value._sfint32; + + case SFSTRING: + { + out << '"'; + for (const char *p = value._sfstring; *p != '\0'; p++) { + if (*p == '"') { + out << "\\\""; + } else { + out << *p; + } + } + return out << '"'; + } + + case SFVEC2F: + return out << value._sfvec[0] << " " << value._sfvec[1]; + + case SFCOLOR: + case SFVEC3F: + return out << value._sfvec[0] << " " << value._sfvec[1] << " " + << value._sfvec[2]; + + case SFROTATION: + return out << value._sfvec[0] << " " << value._sfvec[1] << " " + << value._sfvec[2] << " " << value._sfvec[3]; + + case SFNODE: + switch (value._sfnode._type) { + case SFNodeRef::T_null: + return out << "NULL"; + + case SFNodeRef::T_unnamed: + nassertr(value._sfnode._p != nullptr, out); + value._sfnode._p->output(out, indent); + return out; + + case SFNodeRef::T_def: + out << "DEF " << value._sfnode._name << " "; + value._sfnode._p->output(out, indent); + return out; + + case SFNodeRef::T_use: + return out << "USE " << value._sfnode._name; + } + return out << "(invalid)"; + + case SFIMAGE: + return out << "(image)"; + + case MFCOLOR: + return output_array(out, value._mf, SFCOLOR, indent, 1); + + case MFFLOAT: + return output_array(out, value._mf, SFFLOAT, indent, 5); + + case MFINT32: + return output_array(out, value._mf, SFINT32, indent, 10); + + case MFROTATION: + return output_array(out, value._mf, SFROTATION, indent, 1); + + case MFSTRING: + return output_array(out, value._mf, SFSTRING, indent, 1); + + case MFVEC2F: + return output_array(out, value._mf, SFVEC2F, indent, 1); + + case MFVEC3F: + return output_array(out, value._mf, SFVEC3F, indent, 1); + + case MFNODE: + return output_array(out, value._mf, SFNODE, indent, 1); + } + + return out << "(unknown)"; +} + +VrmlNodeType::VrmlNodeType(const char *nm) +{ + nassertv(nm != nullptr); + name = strdup(nm); +} + +VrmlNodeType::~VrmlNodeType() +{ + free(name); + + // Free strings duplicated when fields/eventIns/eventOuts added: + plist::iterator i; + + for (i = eventIns.begin(); i != eventIns.end(); i++) { + NameTypeRec *r = *i; + free(r->name); + delete r; + } + for (i = eventOuts.begin(); i != eventOuts.end(); i++) { + NameTypeRec *r = *i; + free(r->name); + delete r; + } + for (i = fields.begin(); i != fields.end(); i++) { + NameTypeRec *r = *i; + free(r->name); + delete r; + } +} + +void +VrmlNodeType::addToNameSpace(VrmlNodeType *_type) +{ + if (find(_type->getName()) != nullptr) { + std::cerr << "PROTO " << _type->getName() << " already defined\n"; + return; + } + typeList.push_front(_type); +} + +// +// One list is used to store all the node types. Nested namespaces are +// separated by NULL elements. +// This isn't terribly efficient, but it is nice and simple. +// +void +VrmlNodeType::pushNameSpace() +{ + typeList.push_front(nullptr); +} + +void +VrmlNodeType::popNameSpace() +{ + // Remove everything up to and including the next NULL marker: + plist::iterator i; + for (i = typeList.begin(); i != typeList.end();) { + VrmlNodeType *nodeType = *i; + ++i; + typeList.pop_front(); + + if (nodeType == nullptr) { + break; + } + else { + // NOTE: Instead of just deleting the VrmlNodeTypes, you will + // probably want to reference count or garbage collect them, since + // any nodes created as part of the PROTO implementation will + // probably point back to their VrmlNodeType structure. + delete nodeType; + } + } +} + +const VrmlNodeType * +VrmlNodeType::find(const char *_name) +{ + // Look through the type stack: + plist::iterator i; + for (i = typeList.begin(); i != typeList.end(); i++) { + const VrmlNodeType *nt = *i; + if (nt != nullptr && strcmp(nt->getName(),_name) == 0) { + return nt; + } + } + return nullptr; +} + +void +VrmlNodeType::addEventIn(const char *name, int type, + const VrmlFieldValue *dflt) +{ + add(eventIns, name, type, dflt); +}; +void +VrmlNodeType::addEventOut(const char *name, int type, + const VrmlFieldValue *dflt) +{ + add(eventOuts, name, type, dflt); +}; +void +VrmlNodeType::addField(const char *name, int type, + const VrmlFieldValue *dflt) +{ + add(fields, name, type, dflt); +}; +void +VrmlNodeType::addExposedField(const char *name, int type, + const VrmlFieldValue *dflt) +{ + char tmp[1000]; + add(fields, name, type, dflt); + sprintf(tmp, "set_%s", name); + add(eventIns, tmp, type, dflt); + sprintf(tmp, "%s_changed", name); + add(eventOuts, tmp, type, dflt); +}; + +void +VrmlNodeType::add(plist &recs, const char *name, int type, + const VrmlFieldValue *dflt) +{ + NameTypeRec *r = new NameTypeRec; + r->name = strdup(name); + r->type = type; + if (dflt != nullptr) { + r->dflt = *dflt; + } else { + memset(&r->dflt, 0, sizeof(r->dflt)); + } + recs.push_front(r); +} + +const VrmlNodeType::NameTypeRec * +VrmlNodeType::hasEventIn(const char *name) const +{ + return has(eventIns, name); +} + +const VrmlNodeType::NameTypeRec * +VrmlNodeType::hasEventOut(const char *name) const +{ + return has(eventOuts, name); +} + +const VrmlNodeType::NameTypeRec * +VrmlNodeType::hasField(const char *name) const +{ + return has(fields, name); +} + +const VrmlNodeType::NameTypeRec * +VrmlNodeType::hasExposedField(const char *name) const +{ + // Must have field "name", eventIn "set_name", and eventOut + // "name_changed", all with same type: + char tmp[1000]; + const NameTypeRec *base, *set_name, *name_changed; + + base = has(fields, name); + + sprintf(tmp, "set_%s\n", name); + nassertr(strlen(tmp) < 1000, nullptr); + set_name = has(eventIns, tmp); + + sprintf(tmp, "%s_changed\n", name); + nassertr(strlen(tmp) < 1000, nullptr); + name_changed = has(eventOuts, tmp); + + if (base == nullptr || set_name == nullptr || name_changed == nullptr) { + return nullptr; + } + + if (base->type != set_name->type || base->type != name_changed->type) { + return nullptr; + } + + return base; +} + +const VrmlNodeType::NameTypeRec * +VrmlNodeType::has(const plist &recs, const char *name) const +{ + plist::const_iterator i; + for (i = recs.begin(); i != recs.end(); i++) { + if (strcmp((*i)->name, name) == 0) + return (*i); + } + return nullptr; +} + diff --git a/pandatool/src/vrml/vrmlNodeType.h b/pandatool/src/vrml/vrmlNodeType.h new file mode 100644 index 00000000..1852429a --- /dev/null +++ b/pandatool/src/vrml/vrmlNodeType.h @@ -0,0 +1,108 @@ +/************************************************** + * VRML 2.0 Parser + * Copyright (C) 1996 Silicon Graphics, Inc. + * + * Author(s) : Gavin Bell + * Daniel Woods (first port) + ************************************************** + */ + +#ifndef VRMLNODETYPE_H +#define VRMLNODETYPE_H + +// +// The VrmlNodeType class is responsible for storing information about node +// or prototype types. +// + + +#include "pandatoolbase.h" + +#include "plist.h" +#include "pvector.h" + +class VrmlNode; + +struct SFNodeRef { + VrmlNode *_p; + enum { T_null, T_unnamed, T_def, T_use } _type; + char *_name; +}; + +union VrmlFieldValue { + bool _sfbool; + double _sffloat; + long _sfint32; + char *_sfstring; + double _sfvec[4]; + SFNodeRef _sfnode; + pvector *_mf; +}; + +typedef pvector MFArray; + + +std::ostream &output_value(std::ostream &out, const VrmlFieldValue &value, int type, + int indent = 0); + + +class VrmlNodeType { +public: + // Constructor. Takes name of new type (e.g. "Transform" or "Box") + // Copies the string given as name. + VrmlNodeType(const char *nm); + + // Destructor exists mainly to deallocate storage for name + ~VrmlNodeType(); + + // Namespace management functions. PROTO definitions add node types + // to the namespace. PROTO implementations are a separate node + // namespace, and require that any nested PROTOs NOT be available + // outside the PROTO implementation. + // addToNameSpace will print an error to stderr if the given type + // is already defined. + static void addToNameSpace(VrmlNodeType *); + static void pushNameSpace(); + static void popNameSpace(); + + // Find a node type, given its name. Returns NULL if type is not defined. + static const VrmlNodeType *find(const char *nm); + + // Routines for adding/getting eventIns/Outs/fields + void addEventIn(const char *name, int type, + const VrmlFieldValue *dflt = nullptr); + void addEventOut(const char *name, int type, + const VrmlFieldValue *dflt = nullptr); + void addField(const char *name, int type, + const VrmlFieldValue *dflt = nullptr); + void addExposedField(const char *name, int type, + const VrmlFieldValue *dflt = nullptr); + + typedef struct { + char *name; + int type; + VrmlFieldValue dflt; + } NameTypeRec; + + const NameTypeRec *hasEventIn(const char *name) const; + const NameTypeRec *hasEventOut(const char *name) const; + const NameTypeRec *hasField(const char *name) const; + const NameTypeRec *hasExposedField(const char *name) const; + + const char *getName() const { return name; } + +private: + void add(plist &,const char *,int, const VrmlFieldValue *dflt); + const NameTypeRec *has(const plist &,const char *) const; + + char *name; + + // Node types are stored in this data structure: + static plist typeList; + + plist eventIns; + plist eventOuts; + plist fields; +}; + +#endif diff --git a/pandatool/src/vrml/vrmlParser.cxx.prebuilt b/pandatool/src/vrml/vrmlParser.cxx.prebuilt new file mode 100644 index 00000000..b94bc8d5 --- /dev/null +++ b/pandatool/src/vrml/vrmlParser.cxx.prebuilt @@ -0,0 +1,2387 @@ +/* A Bison parser, made by GNU Bison 2.4.2. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software + Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.4.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse vrmlyyparse +#define yylex vrmlyylex +#define yyerror vrmlyyerror +#define yylval vrmlyylval +#define yychar vrmlyychar +#define yydebug vrmlyydebug +#define yynerrs vrmlyynerrs + + +/* Copy the first part of user declarations. */ + +/* Line 189 of yacc.c */ +#line 23 "vrmlParser.yxx" + + +// +// Parser for VRML 2.0 files. +// This is a minimal parser that does NOT generate an in-memory scene graph. +// + +// The original parser was developed on a Windows 95 PC with +// Borland's C++ 5.0 development tools. This was then ported +// to a Windows 95 PC with Microsoft's MSDEV C++ 4.0 development +// tools. The port introduced the ifdef's for +// USING_BORLAND_CPP_5 : since this provides a "std namespace", +// TWO_ARGUMENTS_FOR_STL_STACK : STL is a moving target. The stack template +// class takes either one or two arguments. + +#include "pandatoolbase.h" +#include "vrmlLexerDefs.h" +#include "vrmlNodeType.h" +#include "vrmlNode.h" +#include "pnotify.h" +#include "plist.h" + +#include +#include // for sprintf() + +//#define YYDEBUG 1 + +// Currently-being-define proto. Prototypes may be nested, so a stack +// is needed: + +std::stack > currentProtoStack; + +// This is used to keep track of which field in which type of node is being +// parsed. Field are nested (nodes are contained inside MFNode/SFNode fields) +// so a stack of these is needed: +typedef struct { + const VrmlNodeType *nodeType; + const char *fieldName; + const VrmlNodeType::NameTypeRec *typeRec; +} FieldRec; + +std::stack > currentField; + +// Similarly for the node entries (which contain the actual values for +// the fields as they are encountered): + +std::stack > currentNode; + +// This is used when the parser knows what kind of token it expects +// to get next-- used when parsing field values (whose types are declared +// and read by the parser) and at certain other places: +extern int expectToken; + +// This is where we store the parsed scene. +VrmlScene *parsed_scene = NULL; + +// Some helper routines defined below: +static void beginProto(const char *); +static void endProto(); +int addField(const char *type, const char *name, const VrmlFieldValue *dflt = NULL); +int addEventIn(const char *type, const char *name, const VrmlFieldValue *dflt = NULL); +int addEventOut(const char *type, const char *name, const VrmlFieldValue *dflt = NULL); +int addExposedField(const char *type, const char *name, const VrmlFieldValue *dflt = NULL); +int add(void (VrmlNodeType::*func)(const char *, int, const VrmlFieldValue *), + const char *typeString, const char *name, + const VrmlFieldValue *dflt); +int fieldType(const char *type); +void enterNode(const char *); +VrmlNode *exitNode(); +void inScript(); +void enterField(const char *); +void storeField(const VrmlFieldValue &value); +void exitField(); +void expect(int type); + +extern void vrmlyyerror(const std::string &); + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +void +vrml_init_parser(std::istream &in, const std::string &filename) { + //yydebug = 0; + vrml_init_lexer(in, filename); +} + +void +vrml_cleanup_parser() { +} + + + +/* Line 189 of yacc.c */ +#line 174 "y.tab.c" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + IDENTIFIER = 258, + DEF = 259, + USE = 260, + PROTO = 261, + EXTERNPROTO = 262, + TO = 263, + IS = 264, + ROUTE = 265, + SFN_NULL = 266, + EVENTIN = 267, + EVENTOUT = 268, + FIELD = 269, + EXPOSEDFIELD = 270, + SFBOOL = 271, + SFCOLOR = 272, + SFFLOAT = 273, + SFIMAGE = 274, + SFINT32 = 275, + SFNODE = 276, + SFROTATION = 277, + SFSTRING = 278, + SFTIME = 279, + SFVEC2F = 280, + SFVEC3F = 281, + MFCOLOR = 282, + MFFLOAT = 283, + MFINT32 = 284, + MFROTATION = 285, + MFSTRING = 286, + MFVEC2F = 287, + MFVEC3F = 288, + MFNODE = 289 + }; +#endif +/* Tokens. */ +#define IDENTIFIER 258 +#define DEF 259 +#define USE 260 +#define PROTO 261 +#define EXTERNPROTO 262 +#define TO 263 +#define IS 264 +#define ROUTE 265 +#define SFN_NULL 266 +#define EVENTIN 267 +#define EVENTOUT 268 +#define FIELD 269 +#define EXPOSEDFIELD 270 +#define SFBOOL 271 +#define SFCOLOR 272 +#define SFFLOAT 273 +#define SFIMAGE 274 +#define SFINT32 275 +#define SFNODE 276 +#define SFROTATION 277 +#define SFSTRING 278 +#define SFTIME 279 +#define SFVEC2F 280 +#define SFVEC3F 281 +#define MFCOLOR 282 +#define MFFLOAT 283 +#define MFINT32 284 +#define MFROTATION 285 +#define MFSTRING 286 +#define MFVEC2F 287 +#define MFVEC3F 288 +#define MFNODE 289 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 214 of yacc.c */ +#line 116 "vrmlParser.yxx" + + char *string; + VrmlFieldValue fv; + VrmlNode *node; + MFArray *mfarray; + SFNodeRef nodeRef; + VrmlScene *scene; + + + +/* Line 214 of yacc.c */ +#line 289 "y.tab.c" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 264 of yacc.c */ +#line 301 "y.tab.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int yyi) +#else +static int +YYID (yyi) + int yyi; +#endif +{ + return yyi; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 3 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 126 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 40 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 26 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 70 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 125 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 289 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 39, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 35, 2, 36, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 37, 2, 38, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 5, 6, 9, 12, 15, 17, 21, + 24, 26, 28, 29, 39, 40, 41, 50, 51, 54, + 58, 62, 63, 69, 70, 76, 77, 80, 84, 88, + 92, 96, 105, 106, 112, 113, 116, 117, 121, 123, + 125, 129, 133, 134, 140, 146, 152, 154, 156, 158, + 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, + 180, 182, 184, 186, 189, 192, 195, 198, 202, 204, + 205 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 41, 0, -1, 42, -1, -1, 42, 43, -1, 42, + 44, -1, 42, 56, -1, 57, -1, 4, 3, 57, + -1, 5, 3, -1, 45, -1, 47, -1, -1, 6, + 3, 46, 35, 50, 36, 37, 42, 38, -1, -1, + -1, 7, 3, 48, 35, 54, 36, 49, 63, -1, + -1, 50, 51, -1, 12, 3, 3, -1, 13, 3, + 3, -1, -1, 14, 3, 3, 52, 63, -1, -1, + 15, 3, 3, 53, 63, -1, -1, 54, 55, -1, + 12, 3, 3, -1, 13, 3, 3, -1, 14, 3, + 3, -1, 15, 3, 3, -1, 10, 3, 39, 3, + 8, 3, 39, 3, -1, -1, 3, 58, 37, 59, + 38, -1, -1, 59, 60, -1, -1, 3, 61, 63, + -1, 56, -1, 44, -1, 12, 3, 3, -1, 13, + 3, 3, -1, -1, 14, 3, 3, 62, 63, -1, + 12, 3, 3, 9, 3, -1, 13, 3, 3, 9, + 3, -1, 16, -1, 17, -1, 27, -1, 18, -1, + 28, -1, 19, -1, 20, -1, 29, -1, 22, -1, + 30, -1, 23, -1, 31, -1, 24, -1, 25, -1, + 32, -1, 26, -1, 33, -1, 21, 43, -1, 21, + 11, -1, 34, 64, -1, 9, 3, -1, 35, 65, + 36, -1, 43, -1, -1, 65, 43, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 147, 147, 155, 158, 165, 166, 170, 176, 182, + 191, 192, 196, 196, 202, 204, 202, 207, 209, 213, + 215, 218, 217, 229, 228, 241, 243, 247, 249, 251, + 253, 258, 263, 263, 267, 269, 273, 273, 280, 281, + 284, 285, 286, 286, 290, 292, 297, 298, 299, 300, + 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, + 311, 312, 313, 315, 316, 322, 323, 327, 331, 342, + 345 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "IDENTIFIER", "DEF", "USE", "PROTO", + "EXTERNPROTO", "TO", "IS", "ROUTE", "SFN_NULL", "EVENTIN", "EVENTOUT", + "FIELD", "EXPOSEDFIELD", "SFBOOL", "SFCOLOR", "SFFLOAT", "SFIMAGE", + "SFINT32", "SFNODE", "SFROTATION", "SFSTRING", "SFTIME", "SFVEC2F", + "SFVEC3F", "MFCOLOR", "MFFLOAT", "MFINT32", "MFROTATION", "MFSTRING", + "MFVEC2F", "MFVEC3F", "MFNODE", "'['", "']'", "'{'", "'}'", "'.'", + "$accept", "vrmlscene", "declarations", "nodeDeclaration", + "protoDeclaration", "proto", "$@1", "externproto", "$@2", "$@3", + "interfaceDeclarations", "interfaceDeclaration", "$@4", "$@5", + "externInterfaceDeclarations", "externInterfaceDeclaration", + "routeDeclaration", "node", "$@6", "nodeGuts", "nodeGut", "$@7", "$@8", + "fieldValue", "mfnodeValue", "nodes", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 91, 93, 123, 125, 46 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 40, 41, 42, 42, 42, 42, 43, 43, 43, + 44, 44, 46, 45, 48, 49, 47, 50, 50, 51, + 51, 52, 51, 53, 51, 54, 54, 55, 55, 55, + 55, 56, 58, 57, 59, 59, 61, 60, 60, 60, + 60, 60, 62, 60, 60, 60, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 64, 64, 65, + 65 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 0, 2, 2, 2, 1, 3, 2, + 1, 1, 0, 9, 0, 0, 8, 0, 2, 3, + 3, 0, 5, 0, 5, 0, 2, 3, 3, 3, + 3, 8, 0, 5, 0, 2, 0, 3, 1, 1, + 3, 3, 0, 5, 5, 5, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 3, 1, 0, + 2 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 3, 0, 2, 1, 32, 0, 0, 0, 0, 0, + 4, 5, 10, 11, 6, 7, 0, 0, 9, 12, + 14, 0, 34, 8, 0, 0, 0, 0, 17, 25, + 0, 36, 0, 0, 0, 33, 39, 38, 35, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 18, 0, 0, 0, 0, 15, 26, 0, 0, + 46, 47, 49, 51, 52, 0, 54, 56, 58, 59, + 61, 48, 50, 53, 55, 57, 60, 62, 0, 37, + 40, 41, 42, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 66, 64, 63, 69, 68, 65, + 0, 0, 0, 19, 20, 21, 23, 0, 27, 28, + 29, 30, 16, 31, 0, 44, 45, 43, 0, 0, + 13, 67, 70, 22, 24 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 2, 10, 11, 12, 24, 13, 25, 92, + 39, 51, 118, 119, 40, 57, 14, 15, 16, 27, + 38, 42, 102, 79, 99, 114 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -73 +static const yytype_int8 yypact[] = +{ + -73, 2, 79, -73, -73, 0, 3, 4, 6, 11, + -73, -73, -73, -73, -73, -73, -16, 26, -73, -73, + -73, -4, -73, -73, 5, 7, 38, -2, -73, -73, + 35, -73, 41, 45, 48, -73, -73, -73, -73, 19, + 66, 50, 43, 51, 54, 84, 85, 87, 88, 89, + 56, -73, 91, 92, 93, 94, -73, -73, 59, 96, + -73, -73, -73, -73, -73, 34, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, -73, -73, 23, -73, + 95, 97, -73, 98, 100, 102, 104, -73, 105, 106, + 107, 108, 43, 109, -73, -73, -73, -73, -73, -73, + 110, 111, 43, -73, -73, -73, -73, 12, -73, -73, + -73, -73, -73, -73, 20, -73, -73, -73, 43, 43, + -73, -73, -73, -73, -73 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -73, -73, 13, -65, 90, -73, -73, -73, -73, -73, + -73, -73, -73, -73, -73, -73, 99, 101, -73, -73, + -73, -73, -73, -72, -73, -73 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -1 +static const yytype_uint8 yytable[] = +{ + 96, 31, 3, 17, 7, 8, 18, 19, 9, 20, + 32, 33, 34, 98, 21, 4, 5, 6, 7, 8, + 112, 22, 9, 4, 5, 6, 4, 5, 6, 4, + 117, 46, 47, 48, 49, 26, 35, 4, 5, 6, + 28, 30, 29, 41, 43, 95, 123, 124, 44, 122, + 120, 45, 59, 58, 80, 50, 121, 81, 97, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 52, 53, + 54, 55, 4, 5, 6, 7, 8, 82, 83, 9, + 84, 85, 86, 87, 88, 89, 90, 91, 93, 94, + 107, 103, 56, 104, 100, 105, 101, 106, 108, 109, + 110, 111, 113, 115, 116, 0, 0, 36, 23, 0, + 0, 0, 0, 0, 0, 0, 37 +}; + +static const yytype_int8 yycheck[] = +{ + 65, 3, 0, 3, 6, 7, 3, 3, 10, 3, + 12, 13, 14, 78, 3, 3, 4, 5, 6, 7, + 92, 37, 10, 3, 4, 5, 3, 4, 5, 3, + 102, 12, 13, 14, 15, 39, 38, 3, 4, 5, + 35, 3, 35, 8, 3, 11, 118, 119, 3, 114, + 38, 3, 9, 3, 3, 36, 36, 3, 35, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 12, 13, + 14, 15, 3, 4, 5, 6, 7, 3, 3, 10, + 3, 3, 3, 37, 3, 3, 3, 3, 39, 3, + 87, 3, 36, 3, 9, 3, 9, 3, 3, 3, + 3, 3, 3, 3, 3, -1, -1, 27, 17, -1, + -1, -1, -1, -1, -1, -1, 27 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 41, 42, 0, 3, 4, 5, 6, 7, 10, + 43, 44, 45, 47, 56, 57, 58, 3, 3, 3, + 3, 3, 37, 57, 46, 48, 39, 59, 35, 35, + 3, 3, 12, 13, 14, 38, 44, 56, 60, 50, + 54, 8, 61, 3, 3, 3, 12, 13, 14, 15, + 36, 51, 12, 13, 14, 15, 36, 55, 3, 9, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 63, + 3, 3, 3, 3, 3, 3, 3, 37, 3, 3, + 3, 3, 49, 39, 3, 11, 43, 35, 43, 64, + 9, 9, 62, 3, 3, 3, 3, 42, 3, 3, + 3, 3, 63, 3, 65, 3, 3, 63, 52, 53, + 38, 36, 43, 63, 63 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. However, + YYFAIL appears to be in use. Nevertheless, it is formally deprecated + in Bison 2.4.2's NEWS entry, where a plan to phase it out is + discussed. */ + +#define YYFAIL goto yyerrlab +#if defined YYFAIL + /* This is here to suppress warnings from the GCC cpp's + -Wunused-macros. Normally we don't worry about that warning, but + some users do, and we want to make it easy for users to remove + YYFAIL uses, which will produce warnings from Bison 2.5. */ +#endif + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +#else +static void +yy_stack_print (yybottom, yytop) + yytype_int16 *yybottom; + yytype_int16 *yytop; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, int yyrule) +#else +static void +yy_reduce_print (yyvsp, yyrule) + YYSTYPE *yyvsp; + int yyrule; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + YYUSE (yyvaluep); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + +/* Prevent warnings from -Wmissing-prototypes. */ +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*-------------------------. +| yyparse or yypush_parse. | +`-------------------------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + `yyss': related to states. + `yyvs': related to semantic values. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yytoken = 0; + yyss = yyssa; + yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: + +/* Line 1464 of yacc.c */ +#line 148 "vrmlParser.yxx" + { + parsed_scene = (yyvsp[(1) - (1)].scene); +} + break; + + case 3: + +/* Line 1464 of yacc.c */ +#line 155 "vrmlParser.yxx" + { + (yyval.scene) = new VrmlScene; +} + break; + + case 4: + +/* Line 1464 of yacc.c */ +#line 159 "vrmlParser.yxx" + { + Declaration d; + d._node = (yyvsp[(2) - (2)].nodeRef); + (yyvsp[(1) - (2)].scene)->push_back(d); + (yyval.scene) = (yyvsp[(1) - (2)].scene); +} + break; + + case 7: + +/* Line 1464 of yacc.c */ +#line 171 "vrmlParser.yxx" + { + (yyval.nodeRef)._p = (yyvsp[(1) - (1)].node); + (yyval.nodeRef)._type = SFNodeRef::T_unnamed; + (yyval.nodeRef)._name = NULL; +} + break; + + case 8: + +/* Line 1464 of yacc.c */ +#line 177 "vrmlParser.yxx" + { + (yyval.nodeRef)._p = (yyvsp[(3) - (3)].node); + (yyval.nodeRef)._type = SFNodeRef::T_def; + (yyval.nodeRef)._name = (yyvsp[(2) - (3)].string); +} + break; + + case 9: + +/* Line 1464 of yacc.c */ +#line 183 "vrmlParser.yxx" + { + (yyval.nodeRef)._p = NULL; + (yyval.nodeRef)._type = SFNodeRef::T_use; + (yyval.nodeRef)._name = (yyvsp[(2) - (2)].string); +} + break; + + case 12: + +/* Line 1464 of yacc.c */ +#line 196 "vrmlParser.yxx" + { beginProto((yyvsp[(2) - (2)].string)); } + break; + + case 13: + +/* Line 1464 of yacc.c */ +#line 198 "vrmlParser.yxx" + { endProto(); free((yyvsp[(2) - (9)].string));} + break; + + case 14: + +/* Line 1464 of yacc.c */ +#line 202 "vrmlParser.yxx" + { beginProto((yyvsp[(2) - (2)].string)); } + break; + + case 15: + +/* Line 1464 of yacc.c */ +#line 204 "vrmlParser.yxx" + { expect(MFSTRING); } + break; + + case 16: + +/* Line 1464 of yacc.c */ +#line 205 "vrmlParser.yxx" + { endProto(); free((yyvsp[(2) - (8)].string)); } + break; + + case 19: + +/* Line 1464 of yacc.c */ +#line 213 "vrmlParser.yxx" + { addEventIn((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 20: + +/* Line 1464 of yacc.c */ +#line 215 "vrmlParser.yxx" + { addEventOut((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 21: + +/* Line 1464 of yacc.c */ +#line 218 "vrmlParser.yxx" + { + int type = fieldType((yyvsp[(2) - (3)].string)); + expect(type); +} + break; + + case 22: + +/* Line 1464 of yacc.c */ +#line 223 "vrmlParser.yxx" + { + addField((yyvsp[(2) - (5)].string), (yyvsp[(3) - (5)].string), &((yyvsp[(5) - (5)].fv))); + free((yyvsp[(2) - (5)].string)); + free((yyvsp[(3) - (5)].string)); +} + break; + + case 23: + +/* Line 1464 of yacc.c */ +#line 229 "vrmlParser.yxx" + { + int type = fieldType((yyvsp[(2) - (3)].string)); + expect(type); +} + break; + + case 24: + +/* Line 1464 of yacc.c */ +#line 234 "vrmlParser.yxx" + { + addExposedField((yyvsp[(2) - (5)].string), (yyvsp[(3) - (5)].string), &((yyvsp[(5) - (5)].fv))); + free((yyvsp[(2) - (5)].string)); + free((yyvsp[(3) - (5)].string)); +} + break; + + case 27: + +/* Line 1464 of yacc.c */ +#line 247 "vrmlParser.yxx" + { addEventIn((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 28: + +/* Line 1464 of yacc.c */ +#line 249 "vrmlParser.yxx" + { addEventOut((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 29: + +/* Line 1464 of yacc.c */ +#line 251 "vrmlParser.yxx" + { addField((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 30: + +/* Line 1464 of yacc.c */ +#line 253 "vrmlParser.yxx" + { addExposedField((yyvsp[(2) - (3)].string), (yyvsp[(3) - (3)].string)); + free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 31: + +/* Line 1464 of yacc.c */ +#line 259 "vrmlParser.yxx" + { free((yyvsp[(2) - (8)].string)); free((yyvsp[(4) - (8)].string)); free((yyvsp[(6) - (8)].string)); free((yyvsp[(8) - (8)].string)); } + break; + + case 32: + +/* Line 1464 of yacc.c */ +#line 263 "vrmlParser.yxx" + { enterNode((yyvsp[(1) - (1)].string)); } + break; + + case 33: + +/* Line 1464 of yacc.c */ +#line 264 "vrmlParser.yxx" + { (yyval.node) = exitNode(); free((yyvsp[(1) - (5)].string));} + break; + + case 36: + +/* Line 1464 of yacc.c */ +#line 273 "vrmlParser.yxx" + { enterField((yyvsp[(1) - (1)].string)); } + break; + + case 37: + +/* Line 1464 of yacc.c */ +#line 275 "vrmlParser.yxx" + { + storeField((yyvsp[(3) - (3)].fv)); + exitField(); + free((yyvsp[(1) - (3)].string)); +} + break; + + case 40: + +/* Line 1464 of yacc.c */ +#line 284 "vrmlParser.yxx" + { inScript(); free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 41: + +/* Line 1464 of yacc.c */ +#line 285 "vrmlParser.yxx" + { inScript(); free((yyvsp[(2) - (3)].string)); free((yyvsp[(3) - (3)].string)); } + break; + + case 42: + +/* Line 1464 of yacc.c */ +#line 286 "vrmlParser.yxx" + { inScript(); + int type = fieldType((yyvsp[(2) - (3)].string)); + expect(type); } + break; + + case 43: + +/* Line 1464 of yacc.c */ +#line 289 "vrmlParser.yxx" + { free((yyvsp[(2) - (5)].string)); free((yyvsp[(3) - (5)].string)); } + break; + + case 44: + +/* Line 1464 of yacc.c */ +#line 291 "vrmlParser.yxx" + { inScript(); free((yyvsp[(2) - (5)].string)); free((yyvsp[(3) - (5)].string)); free((yyvsp[(5) - (5)].string)); } + break; + + case 45: + +/* Line 1464 of yacc.c */ +#line 293 "vrmlParser.yxx" + { inScript(); free((yyvsp[(2) - (5)].string)); free((yyvsp[(3) - (5)].string)); free((yyvsp[(5) - (5)].string)); } + break; + + case 63: + +/* Line 1464 of yacc.c */ +#line 315 "vrmlParser.yxx" + { (yyval.fv)._sfnode = (yyvsp[(2) - (2)].nodeRef); } + break; + + case 64: + +/* Line 1464 of yacc.c */ +#line 317 "vrmlParser.yxx" + { + (yyval.fv)._sfnode._p = NULL; + (yyval.fv)._sfnode._type = SFNodeRef::T_null; + (yyval.fv)._sfnode._name = NULL; +} + break; + + case 65: + +/* Line 1464 of yacc.c */ +#line 322 "vrmlParser.yxx" + { (yyval.fv)._mf = (yyvsp[(2) - (2)].mfarray); } + break; + + case 66: + +/* Line 1464 of yacc.c */ +#line 323 "vrmlParser.yxx" + { free((yyvsp[(2) - (2)].string)); } + break; + + case 67: + +/* Line 1464 of yacc.c */ +#line 328 "vrmlParser.yxx" + { + (yyval.mfarray) = (yyvsp[(2) - (3)].mfarray); +} + break; + + case 68: + +/* Line 1464 of yacc.c */ +#line 332 "vrmlParser.yxx" + { + (yyval.mfarray) = new MFArray; + VrmlFieldValue v; + v._sfnode = (yyvsp[(1) - (1)].nodeRef); + (yyval.mfarray)->push_back(v); +} + break; + + case 69: + +/* Line 1464 of yacc.c */ +#line 342 "vrmlParser.yxx" + { + (yyval.mfarray) = new MFArray; +} + break; + + case 70: + +/* Line 1464 of yacc.c */ +#line 346 "vrmlParser.yxx" + { + VrmlFieldValue v; + v._sfnode = (yyvsp[(2) - (2)].nodeRef); + (yyvsp[(1) - (2)].mfarray)->push_back(v); + (yyval.mfarray) = (yyvsp[(1) - (2)].mfarray); +} + break; + + + +/* Line 1464 of yacc.c */ +#line 1960 "y.tab.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (yymsg); + } + else + { + yyerror (YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined(yyoverflow) || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + +/* Line 1684 of yacc.c */ +#line 354 "vrmlParser.yxx" + + +static void +beginProto(const char *protoName) +{ + // Any protos in the implementation are in a local namespace: + VrmlNodeType::pushNameSpace(); + + VrmlNodeType *t = new VrmlNodeType(protoName); + currentProtoStack.push(t); +} + +static void +endProto() +{ + // Make any protos defined in implementation unavailable: + VrmlNodeType::popNameSpace(); + + // Add this proto definition: + if (currentProtoStack.empty()) { + std::cerr << "Error: Empty PROTO stack!\n"; + } + else { + VrmlNodeType *t = currentProtoStack.top(); + currentProtoStack.pop(); + VrmlNodeType::addToNameSpace(t); + } +} + +int +addField(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addField, type, name, dflt); +} + +int +addEventIn(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addEventIn, type, name, dflt); +} +int +addEventOut(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addEventOut, type, name, dflt); +} +int +addExposedField(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addExposedField, type, name, dflt); +} + +int +add(void (VrmlNodeType::*func)(const char *, int, const VrmlFieldValue *), + const char *typeString, const char *name, + const VrmlFieldValue *dflt) +{ + int type = fieldType(typeString); + + if (type == 0) { + std::cerr << "Error: invalid field type: " << type << "\n"; + } + + // Need to add support for Script nodes: + // if (inScript) ... ??? + + if (currentProtoStack.empty()) { + std::cerr << "Error: declaration outside of prototype\n"; + return 0; + } + VrmlNodeType *t = currentProtoStack.top(); + (t->*func)(name, type, dflt); + + return type; +} + +int +fieldType(const char *type) +{ + if (strcmp(type, "SFBool") == 0) return SFBOOL; + if (strcmp(type, "SFColor") == 0) return SFCOLOR; + if (strcmp(type, "SFFloat") == 0) return SFFLOAT; + if (strcmp(type, "SFImage") == 0) return SFIMAGE; + if (strcmp(type, "SFInt32") == 0) return SFINT32; + if (strcmp(type, "SFNode") == 0) return SFNODE; + if (strcmp(type, "SFRotation") == 0) return SFROTATION; + if (strcmp(type, "SFString") == 0) return SFSTRING; + if (strcmp(type, "SFTime") == 0) return SFTIME; + if (strcmp(type, "SFVec2f") == 0) return SFVEC2F; + if (strcmp(type, "SFVec3f") == 0) return SFVEC3F; + if (strcmp(type, "MFColor") == 0) return MFCOLOR; + if (strcmp(type, "MFFloat") == 0) return MFFLOAT; + if (strcmp(type, "MFInt32") == 0) return MFINT32; + if (strcmp(type, "MFNode") == 0) return MFNODE; + if (strcmp(type, "MFRotation") == 0) return MFROTATION; + if (strcmp(type, "MFString") == 0) return MFSTRING; + if (strcmp(type, "MFVec2f") == 0) return MFVEC2F; + if (strcmp(type, "MFVec3f") == 0) return MFVEC3F; + + std::cerr << "Illegal field type: " << type << "\n"; + + return 0; +} + +void +enterNode(const char *nodeType) +{ + const VrmlNodeType *t = VrmlNodeType::find(nodeType); + if (t == NULL) { + char tmp[1000]; + sprintf(tmp, "Unknown node type '%s'", nodeType); + vrmlyyerror(tmp); + } + FieldRec *fr = new FieldRec; + fr->nodeType = t; + fr->fieldName = NULL; + fr->typeRec = NULL; + currentField.push(fr); + + VrmlNode *node = new VrmlNode(t); + currentNode.push(node); +} + +VrmlNode * +exitNode() +{ + FieldRec *fr = currentField.top(); + nassertr(fr != NULL, NULL); + currentField.pop(); + + VrmlNode *node = currentNode.top(); + nassertr(node != NULL, NULL); + currentNode.pop(); + + // std::cerr << "Just defined node:\n" << *node << "\n\n"; + + delete fr; + return node; +} + +void +inScript() +{ + FieldRec *fr = currentField.top(); + if (fr->nodeType == NULL || + strcmp(fr->nodeType->getName(), "Script") != 0) { + vrmlyyerror("interface declaration outside of Script or prototype"); + } +} + +void +enterField(const char *fieldName) +{ + FieldRec *fr = currentField.top(); + nassertv(fr != NULL); + + fr->fieldName = fieldName; + fr->typeRec = NULL; + if (fr->nodeType != NULL) { + // enterField is called when parsing eventIn and eventOut IS + // declarations, in which case we don't need to do anything special-- + // the IS IDENTIFIER will be returned from the lexer normally. + if (fr->nodeType->hasEventIn(fieldName) || + fr->nodeType->hasEventOut(fieldName)) + return; + + const VrmlNodeType::NameTypeRec *typeRec = + fr->nodeType->hasField(fieldName); + if (typeRec != NULL) { + fr->typeRec = typeRec; + // Let the lexer know what field type to expect: + expect(typeRec->type); + } + else { + std::cerr << "Error: Nodes of type " << fr->nodeType->getName() << + " do not have fields/eventIn/eventOut named " << + fieldName << "\n"; + // expect(ANY_FIELD); + } + } + // else expect(ANY_FIELD); +} + +void +storeField(const VrmlFieldValue &value) { + FieldRec *fr = currentField.top(); + nassertv(fr != NULL); + + VrmlNode *node = currentNode.top(); + nassertv(node != NULL); + + if (fr->typeRec != NULL) { + node->_fields.push_back(VrmlNode::Field(fr->typeRec, value)); + } +} + +void +exitField() +{ + FieldRec *fr = currentField.top(); + nassertv(fr != NULL); + + fr->fieldName = NULL; + fr->typeRec = NULL; +} + +void +expect(int type) +{ + expectToken = type; +} + + diff --git a/pandatool/src/vrml/vrmlParser.h.prebuilt b/pandatool/src/vrml/vrmlParser.h.prebuilt new file mode 100644 index 00000000..d7f8a483 --- /dev/null +++ b/pandatool/src/vrml/vrmlParser.h.prebuilt @@ -0,0 +1,138 @@ +/* A Bison parser, made by GNU Bison 2.4.2. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software + Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + IDENTIFIER = 258, + DEF = 259, + USE = 260, + PROTO = 261, + EXTERNPROTO = 262, + TO = 263, + IS = 264, + ROUTE = 265, + SFN_NULL = 266, + EVENTIN = 267, + EVENTOUT = 268, + FIELD = 269, + EXPOSEDFIELD = 270, + SFBOOL = 271, + SFCOLOR = 272, + SFFLOAT = 273, + SFIMAGE = 274, + SFINT32 = 275, + SFNODE = 276, + SFROTATION = 277, + SFSTRING = 278, + SFTIME = 279, + SFVEC2F = 280, + SFVEC3F = 281, + MFCOLOR = 282, + MFFLOAT = 283, + MFINT32 = 284, + MFROTATION = 285, + MFSTRING = 286, + MFVEC2F = 287, + MFVEC3F = 288, + MFNODE = 289 + }; +#endif +/* Tokens. */ +#define IDENTIFIER 258 +#define DEF 259 +#define USE 260 +#define PROTO 261 +#define EXTERNPROTO 262 +#define TO 263 +#define IS 264 +#define ROUTE 265 +#define SFN_NULL 266 +#define EVENTIN 267 +#define EVENTOUT 268 +#define FIELD 269 +#define EXPOSEDFIELD 270 +#define SFBOOL 271 +#define SFCOLOR 272 +#define SFFLOAT 273 +#define SFIMAGE 274 +#define SFINT32 275 +#define SFNODE 276 +#define SFROTATION 277 +#define SFSTRING 278 +#define SFTIME 279 +#define SFVEC2F 280 +#define SFVEC3F 281 +#define MFCOLOR 282 +#define MFFLOAT 283 +#define MFINT32 284 +#define MFROTATION 285 +#define MFSTRING 286 +#define MFVEC2F 287 +#define MFVEC3F 288 +#define MFNODE 289 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +{ + +/* Line 1685 of yacc.c */ +#line 116 "vrmlParser.yxx" + + char *string; + VrmlFieldValue fv; + VrmlNode *node; + MFArray *mfarray; + SFNodeRef nodeRef; + VrmlScene *scene; + + + +/* Line 1685 of yacc.c */ +#line 130 "y.tab.h" +} YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + +extern YYSTYPE vrmlyylval; + + diff --git a/pandatool/src/vrml/vrmlParser.yxx b/pandatool/src/vrml/vrmlParser.yxx new file mode 100644 index 00000000..9bbdda7b --- /dev/null +++ b/pandatool/src/vrml/vrmlParser.yxx @@ -0,0 +1,567 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlParser.yxx + * @author drose + * @date 2004-10-01 + */ + +// ******************************************************* +// VRML 2.0 Parser +// Copyright (C) 1996 Silicon Graphics, Inc. +// +// Author(s) : Gavin Bell +// Daniel Woods (first port, minor fixes) +// ******************************************************* +// +%{ + +// +// Parser for VRML 2.0 files. +// This is a minimal parser that does NOT generate an in-memory scene graph. +// + +// The original parser was developed on a Windows 95 PC with +// Borland's C++ 5.0 development tools. This was then ported +// to a Windows 95 PC with Microsoft's MSDEV C++ 4.0 development +// tools. The port introduced the ifdef's for +// USING_BORLAND_CPP_5 : since this provides a "std namespace", +// TWO_ARGUMENTS_FOR_STL_STACK : STL is a moving target. The stack template +// class takes either one or two arguments. + +#include "pandatoolbase.h" +#include "vrmlLexerDefs.h" +#include "vrmlNodeType.h" +#include "vrmlNode.h" +#include "pnotify.h" +#include "plist.h" + +#include +#include // for sprintf() + +//#define YYDEBUG 1 + +// Currently-being-define proto. Prototypes may be nested, so a stack +// is needed: + +std::stack > currentProtoStack; + +// This is used to keep track of which field in which type of node is being +// parsed. Field are nested (nodes are contained inside MFNode/SFNode fields) +// so a stack of these is needed: +typedef struct { + const VrmlNodeType *nodeType; + const char *fieldName; + const VrmlNodeType::NameTypeRec *typeRec; +} FieldRec; + +std::stack > currentField; + +// Similarly for the node entries (which contain the actual values for +// the fields as they are encountered): + +std::stack > currentNode; + +// This is used when the parser knows what kind of token it expects +// to get next-- used when parsing field values (whose types are declared +// and read by the parser) and at certain other places: +extern int expectToken; + +// This is where we store the parsed scene. +VrmlScene *parsed_scene = nullptr; + +// Some helper routines defined below: +static void beginProto(const char *); +static void endProto(); +int addField(const char *type, const char *name, const VrmlFieldValue *dflt = nullptr); +int addEventIn(const char *type, const char *name, const VrmlFieldValue *dflt = nullptr); +int addEventOut(const char *type, const char *name, const VrmlFieldValue *dflt = nullptr); +int addExposedField(const char *type, const char *name, const VrmlFieldValue *dflt = nullptr); +int add(void (VrmlNodeType::*func)(const char *, int, const VrmlFieldValue *), + const char *typeString, const char *name, + const VrmlFieldValue *dflt); +int fieldType(const char *type); +void enterNode(const char *); +VrmlNode *exitNode(); +void inScript(); +void enterField(const char *); +void storeField(const VrmlFieldValue &value); +void exitField(); +void expect(int type); + +extern void vrmlyyerror(const std::string &); + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +void +vrml_init_parser(std::istream &in, const std::string &filename) { + //yydebug = 0; + vrml_init_lexer(in, filename); +} + +void +vrml_cleanup_parser() { +} + +%} + +%union { + char *string; + VrmlFieldValue fv; + VrmlNode *node; + MFArray *mfarray; + SFNodeRef nodeRef; + VrmlScene *scene; +}; + +%type fieldValue +%type node +%type nodeDeclaration +%type mfnodeValue nodes +%type declarations + +/* + * And types that will be needed by a true VRML implementation: + * %type vrmlscene declarations + */ + +%token IDENTIFIER +%token DEF USE PROTO EXTERNPROTO TO IS ROUTE SFN_NULL +%token EVENTIN EVENTOUT FIELD EXPOSEDFIELD + +%token SFBOOL SFCOLOR SFFLOAT SFIMAGE SFINT32 SFNODE SFROTATION +%token SFSTRING SFTIME SFVEC2F SFVEC3F +%token MFCOLOR MFFLOAT MFINT32 MFROTATION MFSTRING MFVEC2F MFVEC3F +%token MFNODE + +%% + +vrmlscene: declarations +{ + parsed_scene = $1; +} + ; + +declarations: + /* Empty is OK */ +{ + $$ = new VrmlScene; +} + | declarations nodeDeclaration +{ + Declaration d; + d._node = $2; + $1->push_back(d); + $$ = $1; +} + | declarations protoDeclaration + | declarations routeDeclaration + ; + +nodeDeclaration: + node +{ + $$._p = $1; + $$._type = SFNodeRef::T_unnamed; + $$._name = nullptr; +} + | DEF IDENTIFIER node +{ + $$._p = $3; + $$._type = SFNodeRef::T_def; + $$._name = $2; +} + | USE IDENTIFIER +{ + $$._p = nullptr; + $$._type = SFNodeRef::T_use; + $$._name = $2; +} + ; + +protoDeclaration: + proto + | externproto + ; + +proto: + PROTO IDENTIFIER { beginProto($2); } + '[' interfaceDeclarations ']' + '{' declarations '}' { endProto(); free($2);} + ; + +externproto: + EXTERNPROTO IDENTIFIER { beginProto($2); } + '[' externInterfaceDeclarations ']' + { expect(MFSTRING); } + fieldValue { endProto(); free($2); } + ; +interfaceDeclarations: + /* Empty is OK */ + | interfaceDeclarations interfaceDeclaration + ; + +interfaceDeclaration: + EVENTIN IDENTIFIER IDENTIFIER { addEventIn($2, $3); + free($2); free($3); } + | EVENTOUT IDENTIFIER IDENTIFIER { addEventOut($2, $3); + free($2); free($3); } + | FIELD IDENTIFIER IDENTIFIER +{ + int type = fieldType($2); + expect(type); +} + fieldValue +{ + addField($2, $3, &($5)); + free($2); + free($3); +} + | EXPOSEDFIELD IDENTIFIER IDENTIFIER +{ + int type = fieldType($2); + expect(type); +} + fieldValue +{ + addExposedField($2, $3, &($5)); + free($2); + free($3); +} + ; + +externInterfaceDeclarations: + /* Empty is OK */ + | externInterfaceDeclarations externInterfaceDeclaration + ; + +externInterfaceDeclaration: + EVENTIN IDENTIFIER IDENTIFIER { addEventIn($2, $3); + free($2); free($3); } + | EVENTOUT IDENTIFIER IDENTIFIER { addEventOut($2, $3); + free($2); free($3); } + | FIELD IDENTIFIER IDENTIFIER { addField($2, $3); + free($2); free($3); } + | EXPOSEDFIELD IDENTIFIER IDENTIFIER { addExposedField($2, $3); + free($2); free($3); } + ; + +routeDeclaration: + ROUTE IDENTIFIER '.' IDENTIFIER TO IDENTIFIER '.' IDENTIFIER + { free($2); free($4); free($6); free($8); } + ; + +node: + IDENTIFIER { enterNode($1); } + '{' nodeGuts '}' { $$ = exitNode(); free($1);} + ; + +nodeGuts: + /* Empty is OK */ + | nodeGuts nodeGut + ; + +nodeGut: + IDENTIFIER { enterField($1); } + fieldValue +{ + storeField($3); + exitField(); + free($1); +} + | routeDeclaration + | protoDeclaration + + /* The following are only valid for Script nodes: */ + | EVENTIN IDENTIFIER IDENTIFIER { inScript(); free($2); free($3); } + | EVENTOUT IDENTIFIER IDENTIFIER { inScript(); free($2); free($3); } + | FIELD IDENTIFIER IDENTIFIER { inScript(); + int type = fieldType($2); + expect(type); } + fieldValue { free($2); free($3); } + | EVENTIN IDENTIFIER IDENTIFIER IS IDENTIFIER + { inScript(); free($2); free($3); free($5); } + | EVENTOUT IDENTIFIER IDENTIFIER IS IDENTIFIER + { inScript(); free($2); free($3); free($5); } + ; + +fieldValue: + SFBOOL + | SFCOLOR + | MFCOLOR + | SFFLOAT + | MFFLOAT + | SFIMAGE + | SFINT32 + | MFINT32 + | SFROTATION + | MFROTATION + | SFSTRING + | MFSTRING + | SFTIME + | SFVEC2F + | MFVEC2F + | SFVEC3F + | MFVEC3F + + | SFNODE nodeDeclaration { $$._sfnode = $2; } + | SFNODE SFN_NULL +{ + $$._sfnode._p = nullptr; + $$._sfnode._type = SFNodeRef::T_null; + $$._sfnode._name = nullptr; +} + | MFNODE mfnodeValue { $$._mf = $2; } + | IS IDENTIFIER { free($2); } + ; + +mfnodeValue: + '[' nodes ']' +{ + $$ = $2; +} + | nodeDeclaration +{ + $$ = new MFArray; + VrmlFieldValue v; + v._sfnode = $1; + $$->push_back(v); +} + ; + +nodes: + /* Empty is OK */ +{ + $$ = new MFArray; +} + | nodes nodeDeclaration +{ + VrmlFieldValue v; + v._sfnode = $2; + $1->push_back(v); + $$ = $1; +} + ; + +%% + +static void +beginProto(const char *protoName) +{ + // Any protos in the implementation are in a local namespace: + VrmlNodeType::pushNameSpace(); + + VrmlNodeType *t = new VrmlNodeType(protoName); + currentProtoStack.push(t); +} + +static void +endProto() +{ + // Make any protos defined in implementation unavailable: + VrmlNodeType::popNameSpace(); + + // Add this proto definition: + if (currentProtoStack.empty()) { + std::cerr << "Error: Empty PROTO stack!\n"; + } + else { + VrmlNodeType *t = currentProtoStack.top(); + currentProtoStack.pop(); + VrmlNodeType::addToNameSpace(t); + } +} + +int +addField(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addField, type, name, dflt); +} + +int +addEventIn(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addEventIn, type, name, dflt); +} +int +addEventOut(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addEventOut, type, name, dflt); +} +int +addExposedField(const char *type, const char *name, + const VrmlFieldValue *dflt) +{ + return add(&VrmlNodeType::addExposedField, type, name, dflt); +} + +int +add(void (VrmlNodeType::*func)(const char *, int, const VrmlFieldValue *), + const char *typeString, const char *name, + const VrmlFieldValue *dflt) +{ + int type = fieldType(typeString); + + if (type == 0) { + std::cerr << "Error: invalid field type: " << type << "\n"; + } + + // Need to add support for Script nodes: + // if (inScript) ... ??? + + if (currentProtoStack.empty()) { + std::cerr << "Error: declaration outside of prototype\n"; + return 0; + } + VrmlNodeType *t = currentProtoStack.top(); + (t->*func)(name, type, dflt); + + return type; +} + +int +fieldType(const char *type) +{ + if (strcmp(type, "SFBool") == 0) return SFBOOL; + if (strcmp(type, "SFColor") == 0) return SFCOLOR; + if (strcmp(type, "SFFloat") == 0) return SFFLOAT; + if (strcmp(type, "SFImage") == 0) return SFIMAGE; + if (strcmp(type, "SFInt32") == 0) return SFINT32; + if (strcmp(type, "SFNode") == 0) return SFNODE; + if (strcmp(type, "SFRotation") == 0) return SFROTATION; + if (strcmp(type, "SFString") == 0) return SFSTRING; + if (strcmp(type, "SFTime") == 0) return SFTIME; + if (strcmp(type, "SFVec2f") == 0) return SFVEC2F; + if (strcmp(type, "SFVec3f") == 0) return SFVEC3F; + if (strcmp(type, "MFColor") == 0) return MFCOLOR; + if (strcmp(type, "MFFloat") == 0) return MFFLOAT; + if (strcmp(type, "MFInt32") == 0) return MFINT32; + if (strcmp(type, "MFNode") == 0) return MFNODE; + if (strcmp(type, "MFRotation") == 0) return MFROTATION; + if (strcmp(type, "MFString") == 0) return MFSTRING; + if (strcmp(type, "MFVec2f") == 0) return MFVEC2F; + if (strcmp(type, "MFVec3f") == 0) return MFVEC3F; + + std::cerr << "Illegal field type: " << type << "\n"; + + return 0; +} + +void +enterNode(const char *nodeType) +{ + const VrmlNodeType *t = VrmlNodeType::find(nodeType); + if (t == nullptr) { + char tmp[1000]; + sprintf(tmp, "Unknown node type '%s'", nodeType); + vrmlyyerror(tmp); + } + FieldRec *fr = new FieldRec; + fr->nodeType = t; + fr->fieldName = nullptr; + fr->typeRec = nullptr; + currentField.push(fr); + + VrmlNode *node = new VrmlNode(t); + currentNode.push(node); +} + +VrmlNode * +exitNode() +{ + FieldRec *fr = currentField.top(); + nassertr(fr != nullptr, nullptr); + currentField.pop(); + + VrmlNode *node = currentNode.top(); + nassertr(node != nullptr, nullptr); + currentNode.pop(); + + // std::cerr << "Just defined node:\n" << *node << "\n\n"; + + delete fr; + return node; +} + +void +inScript() +{ + FieldRec *fr = currentField.top(); + if (fr->nodeType == nullptr || + strcmp(fr->nodeType->getName(), "Script") != 0) { + vrmlyyerror("interface declaration outside of Script or prototype"); + } +} + +void +enterField(const char *fieldName) +{ + FieldRec *fr = currentField.top(); + nassertv(fr != nullptr); + + fr->fieldName = fieldName; + fr->typeRec = nullptr; + if (fr->nodeType != nullptr) { + // enterField is called when parsing eventIn and eventOut IS + // declarations, in which case we don't need to do anything special-- + // the IS IDENTIFIER will be returned from the lexer normally. + if (fr->nodeType->hasEventIn(fieldName) || + fr->nodeType->hasEventOut(fieldName)) + return; + + const VrmlNodeType::NameTypeRec *typeRec = + fr->nodeType->hasField(fieldName); + if (typeRec != nullptr) { + fr->typeRec = typeRec; + // Let the lexer know what field type to expect: + expect(typeRec->type); + } + else { + std::cerr << "Error: Nodes of type " << fr->nodeType->getName() << + " do not have fields/eventIn/eventOut named " << + fieldName << "\n"; + // expect(ANY_FIELD); + } + } + // else expect(ANY_FIELD); +} + +void +storeField(const VrmlFieldValue &value) { + FieldRec *fr = currentField.top(); + nassertv(fr != nullptr); + + VrmlNode *node = currentNode.top(); + nassertv(node != nullptr); + + if (fr->typeRec != nullptr) { + node->_fields.push_back(VrmlNode::Field(fr->typeRec, value)); + } +} + +void +exitField() +{ + FieldRec *fr = currentField.top(); + nassertv(fr != nullptr); + + fr->fieldName = nullptr; + fr->typeRec = nullptr; +} + +void +expect(int type) +{ + expectToken = type; +} + diff --git a/pandatool/src/vrml/vrmlParserDefs.h b/pandatool/src/vrml/vrmlParserDefs.h new file mode 100644 index 00000000..cad0d5b2 --- /dev/null +++ b/pandatool/src/vrml/vrmlParserDefs.h @@ -0,0 +1,23 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlParserDefs.h + * @author drose + * @date 2004-09-30 + */ + +#ifndef VRMLPARSERDEFS_H +#define VRMLPARSERDEFS_H + +#include "pandatoolbase.h" + +void vrml_init_parser(std::istream &in, const std::string &filename); +void vrml_cleanup_parser(); +int vrmlyyparse(); + +#endif diff --git a/pandatool/src/vrmlegg/CMakeLists.txt b/pandatool/src/vrmlegg/CMakeLists.txt new file mode 100644 index 00000000..b2a38c84 --- /dev/null +++ b/pandatool/src/vrmlegg/CMakeLists.txt @@ -0,0 +1,22 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3VRMLEGG_HEADERS + indexedFaceSet.h + vrmlAppearance.h + vrmlToEggConverter.h +) + +set(P3VRMLEGG_SOURCES + indexedFaceSet.cxx + vrmlAppearance.cxx + vrmlToEggConverter.cxx +) + +composite_sources(p3vrmlegg P3VRMLEGG_SOURCES) +add_library(p3vrmlegg STATIC ${P3VRMLEGG_HEADERS} ${P3VRMLEGG_SOURCES}) +target_link_libraries(p3vrmlegg p3vrml p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/vrmlegg/indexedFaceSet.cxx b/pandatool/src/vrmlegg/indexedFaceSet.cxx new file mode 100644 index 00000000..0f0dce8e --- /dev/null +++ b/pandatool/src/vrmlegg/indexedFaceSet.cxx @@ -0,0 +1,549 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file indexedFaceSet.cxx + * @author drose + * @date 1999-06-24 + */ + +#include "indexedFaceSet.h" +#include "vrmlAppearance.h" +#include "vrmlNodeType.h" +#include "vrmlNode.h" +#include "pnotify.h" + +#include "eggGroup.h" +#include "eggVertex.h" +#include "eggVertexPool.h" +#include "eggPolygon.h" + +using std::cerr; + +/** + * + */ +IndexedFaceSet:: +IndexedFaceSet(const VrmlNode *geometry, const VRMLAppearance &appearance) : + _geometry(geometry), _appearance(appearance) +{ + get_coord_values(); + get_polys(); + get_colors(); + _has_normals = get_normals(); + if (!_per_vertex_normals.empty()) { + assign_per_vertex_normals(); + } + get_uvs(); + if (!_per_vertex_uvs.empty()) { + assign_per_vertex_uvs(); + } +} + + +/** + * + */ +void IndexedFaceSet:: +convert_to_egg(EggGroup *group, const LMatrix4d &net_transform) { + EggVertexPool *vpool = new EggVertexPool(group->get_name()); + group->add_child(vpool); + + make_polys(vpool, group, net_transform); + if (!_has_normals && _appearance._has_material) { + compute_normals(group); + } +} + + +/** + * + */ +void IndexedFaceSet:: +get_coord_values() { + const VrmlNode *coord = _geometry->get_value("coord")._sfnode._p; + + if (coord != nullptr) { + const MFArray *point = coord->get_value("point")._mf; + MFArray::const_iterator ci; + for (ci = point->begin(); ci != point->end(); ++ci) { + const double *p = (*ci)._sfvec; + _coord_values.push_back(LVertexd(p[0], p[1], p[2])); + } + } +} + + +/** + * + */ +void IndexedFaceSet:: +get_polys() { + const MFArray *coordIndex = _geometry->get_value("coordIndex")._mf; + VrmlPolygon poly; + + MFArray::const_iterator ci; + for (ci = coordIndex->begin(); ci != coordIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0) { + _polys.push_back(poly); + poly._verts.clear(); + } else { + const LVertexd &p = _coord_values[(*ci)._sfint32]; + VrmlVertex vert; + vert._index = (*ci)._sfint32; + vert._pos = p; + poly._verts.push_back(vert); + } + } +} + +/** + * Builds up a vector of LColor pointers corresponding to the VRML color node. + */ +void IndexedFaceSet:: +get_vrml_colors(const VrmlNode *color_node, double transparency, + pvector &color_list) { + const MFArray *color = color_node->get_value("color")._mf; + MFArray::const_iterator ci; + for (ci = color->begin(); ci != color->end(); ++ci) { + const double *p = (*ci)._sfvec; + LColor color(p[0], p[1], p[2], 1.0 - transparency); + color_list.push_back(color); + } +} + +/** + * Builds up a vector of double array pointers corresponding to the VRML + * normal node. + */ +void IndexedFaceSet:: +get_vrml_normals(const VrmlNode *normal_node, + pvector &normal_list) { + const MFArray *point = normal_node->get_value("vector")._mf; + MFArray::const_iterator ci; + for (ci = point->begin(); ci != point->end(); ++ci) { + const double *p = (*ci)._sfvec; + LNormald normal(p[0], p[1], p[2]); + normal_list.push_back(normal); + } +} + +/** + * Builds up a vector of double array pointers corresponding to the VRML + * texCoord node. + */ +void IndexedFaceSet:: +get_vrml_uvs(const VrmlNode *texCoord_node, + pvector &uv_list) { + const MFArray *point = texCoord_node->get_value("point")._mf; + MFArray::const_iterator ci; + for (ci = point->begin(); ci != point->end(); ++ci) { + const double *p = (*ci)._sfvec; + LTexCoordd uv(p[0], p[1]); + uv_list.push_back(uv); + } +} + + +/** + * + */ +bool IndexedFaceSet:: +get_colors() { + const VrmlNode *color = _geometry->get_value("color")._sfnode._p; + if (color != nullptr) { + // Vertex or face colors. + pvector color_list; + get_vrml_colors(color, _appearance._transparency, color_list); + + bool colorPerVertex = _geometry->get_value("colorPerVertex")._sfbool; + MFArray *colorIndex = _geometry->get_value("colorIndex")._mf; + if (colorPerVertex) { + MFArray::const_iterator ci; + size_t pi = 0; + size_t pv = 0; + for (ci = colorIndex->begin(); ci != colorIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0) { + // End of poly. + if (pv != _polys[pi]._verts.size()) { + cerr << "Color indices don't match up!\n"; + return false; + } + pi++; + pv = 0; + } else { + if (pi >= _polys.size() || pv >= _polys[pi]._verts.size()) { + cerr << "Color indices don't match up!\n"; + return false; + } + _polys[pi]._verts[pv]._attrib.set_color(color_list[(*ci)._sfint32]); + pv++; + } + } + if (pi != _polys.size()) { + cerr << "Not enough color indices!\n"; + return false; + } + } else { + if (!colorIndex->empty()) { + MFArray::const_iterator ci; + size_t pi = 0; + if (colorIndex->size() != _polys.size()) { + cerr << "Wrong number of color indices!\n"; + return false; + } + for (ci = colorIndex->begin(); ci != colorIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0 || (*ci)._sfint32 >= (int)color_list.size()) { + cerr << "Invalid color index!\n"; + return false; + } + _polys[pi]._attrib.set_color(color_list[(*ci)._sfint32]); + pi++; + } + } else { + if (color_list.size() != _polys.size()) { + cerr << "Wrong number of colors!\n"; + return false; + } + for (size_t pi = 0; pi < color_list.size(); pi++) { + _polys[pi]._attrib.set_color(color_list[pi]); + } + } + } + return true; + } + return false; +} + +/** + * + */ +bool IndexedFaceSet:: +get_normals() { + const VrmlNode *normal = _geometry->get_value("normal")._sfnode._p; + if (normal != nullptr) { + // Vertex or face normals. + pvector normal_list; + get_vrml_normals(normal, normal_list); + + bool normalPerVertex = _geometry->get_value("normalPerVertex")._sfbool; + MFArray *normalIndex = _geometry->get_value("normalIndex")._mf; + MFArray::const_iterator ci; + + if (normalPerVertex && + normal_list.size() == _polys.size() && + normalIndex->empty()) { + // Here's an interesting formZ bug. We end up with a VRML file that + // claims to have normals per vertex, yet there is no normal index list, + // and there are exactly enough normals in the list to indicate one + // normal per face. Silly formZ. + normalPerVertex = false; + } + + if (normalPerVertex) { + + if (normalIndex->empty()) { + // If we have *no* normal index array, but we do have per-vertex + // normals, assume the VRML writer meant to imply a one-to-one + // mapping. This works around a broken formZ VRML file writer. + for (size_t i = 0; i < normal_list.size(); i++) { + VrmlFieldValue fv; + fv._sfint32 = i; + (*normalIndex).push_back(fv); + } + } + + // It's possible that this .wrl file indexes normals directly into the + // vertex array, instead of into the polygon list. Check for this + // possibility. This can only happen if the number of normal indices + // exactly matches the number of vertices, and none of the indices is + // -1. + bool linear_list = (normalIndex->size() == _coord_values.size()); + for (ci = normalIndex->begin(); + ci != normalIndex->end() && linear_list; + ++ci) { + linear_list = ((*ci)._sfint32 >= 0); + } + + if (linear_list) { + // Ok, we do have such a list. This .wrl file seems to store its + // texture coordinates one per vertex, instead of one per polygon + // vertex. + _per_vertex_normals.reserve(_coord_values.size()); + + for (ci = normalIndex->begin(); ci != normalIndex->end(); ++ci) { + size_t vi = (*ci)._sfint32; + nassertr(vi >= 0, false); + if (vi >= normal_list.size()) { + cerr << "Invalid normal index: " << vi << "\n"; + return false; + } + _per_vertex_normals.push_back(normal_list[vi]); + } + nassertr(_per_vertex_normals.size() == _coord_values.size(), false); + + } else { + // This is a "correct" .wrl file that stores its texture coordinates + // one per polygon vertex. This allows a shared vertex to contain two + // different normal values in differing polygons (meaning it's not + // actually shared). + + MFArray::const_iterator ci; + size_t pi = 0; + size_t pv = 0; + for (ci = normalIndex->begin(); ci != normalIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0) { + // End of poly. + if (pv != _polys[pi]._verts.size()) { + cerr << "Normal indices don't match up!\n"; + return false; + } + pi++; + pv = 0; + } else { + if (pi >= _polys.size() || pv >= _polys[pi]._verts.size()) { + cerr << "Normal indices don't match up!\n"; + return false; + } + const LNormald &d = normal_list[(*ci)._sfint32]; + _polys[pi]._verts[pv]._attrib.set_normal(d); + pv++; + } + } + if (pi != _polys.size()) { + cerr << "Not enough normal indices!\n"; + return false; + } + } + } else { + if (!normalIndex->empty()) { + size_t pi = 0; + if (normalIndex->size() != _polys.size()) { + cerr << "Wrong number of normal indices!\n"; + return false; + } + for (ci = normalIndex->begin(); ci != normalIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0 || (*ci)._sfint32 >= (int)normal_list.size()) { + cerr << "Invalid normal index!\n"; + return false; + } + const LNormald &d = normal_list[(*ci)._sfint32]; + _polys[pi]._attrib.set_normal(d); + pi++; + } + } else { + if (normal_list.size() != _polys.size()) { + cerr << "Wrong number of normals!\n"; + return false; + } + for (size_t pi = 0; pi < normal_list.size(); pi++) { + const LNormald &d = normal_list[pi]; + _polys[pi]._attrib.set_normal(d); + } + } + } + return true; + } + return false; +} + +/** + * Once the array of _per_vertex_normals has been filled (by a broken .wrl + * file that indexes the normal's directly into the vertex array instead of + * per polygon vertex), go back through the polygons and assign the normals by + * index number. + */ +void IndexedFaceSet:: +assign_per_vertex_normals() { + for (size_t pi = 0; pi < _polys.size(); pi++) { + for (size_t pv = 0; pv < _polys[pi]._verts.size(); pv++) { + VrmlVertex &vv = _polys[pi]._verts[pv]; + if (vv._index >= 0 && vv._index < (int)_per_vertex_normals.size()) { + const LNormald &d = _per_vertex_normals[vv._index]; + vv._attrib.set_normal(d); + } + } + } +} + + +/** + * + */ +bool IndexedFaceSet:: +get_uvs() { + const VrmlNode *texCoord = _geometry->get_value("texCoord")._sfnode._p; + if (texCoord != nullptr) { + // Vertex or face texCoords. + pvector uv_list; + get_vrml_uvs(texCoord, uv_list); + + MFArray *texCoordIndex = _geometry->get_value("texCoordIndex")._mf; + MFArray::const_iterator ci; + + if (texCoordIndex->empty()) { + // If we have *no* texture coordinate index array, but we do have + // texture coordinates, assume the VRML writer meant to imply a one-to- + // one mapping. This works around a broken formZ VRML file writer. + for (size_t i = 0; i < uv_list.size(); i++) { + VrmlFieldValue fv; + fv._sfint32 = i; + (*texCoordIndex).push_back(fv); + } + } + + // It's possible that this .wrl file indexes texture coordinates directly + // into the vertex array, instead of into the polygon list. Check for + // this possibility. This can only happen if the number of texture + // coordinate indices exactly matches the number of vertices, and none of + // the indices is -1. + bool linear_list = (texCoordIndex->size() == _coord_values.size()); + for (ci = texCoordIndex->begin(); + ci != texCoordIndex->end() && linear_list; + ++ci) { + linear_list = ((*ci)._sfint32 >= 0); + } + + if (linear_list) { + // Ok, we do have such a list. This .wrl file seems to store its + // texture coordinates one per vertex, instead of one per polygon + // vertex. + _per_vertex_uvs.reserve(_coord_values.size()); + + for (ci = texCoordIndex->begin(); ci != texCoordIndex->end(); ++ci) { + size_t vi = (*ci)._sfint32; + nassertr(vi >= 0, false); + if (vi >= uv_list.size()) { + cerr << "Invalid texCoord index: " << vi << "\n"; + return false; + } + _per_vertex_uvs.push_back(uv_list[vi]); + } + nassertr(_per_vertex_uvs.size() == _coord_values.size(), false); + + } else { + // This is a "correct" .wrl file that stores its texture coordinates one + // per polygon vertex. This allows a shared vertex to contain two + // different texture coordinate values in differing polygons (meaning + // it's not actually shared). + + size_t pi = 0; + size_t pv = 0; + for (ci = texCoordIndex->begin(); ci != texCoordIndex->end(); ++ci) { + if ((*ci)._sfint32 < 0) { + // End of poly. + if (pv != _polys[pi]._verts.size()) { + cerr << "texCoord indices don't match up!\n"; + return false; + } + pi++; + pv = 0; + } else { + if (pi >= _polys.size() || pv >= _polys[pi]._verts.size()) { + cerr << "texCoord indices don't match up!\n"; + return false; + } + _polys[pi]._verts[pv]._attrib.set_uv(uv_list[(*ci)._sfint32]); + pv++; + } + } + if (pi != _polys.size()) { + cerr << "Not enough texCoord indices!\n"; + return false; + } + } + return true; + } + return false; +} + +/** + * Once the array of _per_vertex_uvs has been filled (by a broken .wrl file + * that indexes the uv's directly into the vertex array instead of per polygon + * vertex), go back through the polygons and assign the UV's by index number. + */ +void IndexedFaceSet:: +assign_per_vertex_uvs() { + for (size_t pi = 0; pi < _polys.size(); pi++) { + for (size_t pv = 0; pv < _polys[pi]._verts.size(); pv++) { + VrmlVertex &vv = _polys[pi]._verts[pv]; + if (vv._index >= 0 && vv._index < (int)_per_vertex_uvs.size()) { + const LTexCoordd &d = _per_vertex_uvs[vv._index]; + vv._attrib.set_uv(d); + } + } + } +} + + +/** + * + */ +void IndexedFaceSet:: +make_polys(EggVertexPool *vpool, EggGroup *group, + const LMatrix4d &net_transform) { + bool ccw = _geometry->get_value("ccw")._sfbool; + bool solid = _geometry->get_value("solid")._sfbool; + + for (size_t pi = 0; pi < _polys.size(); pi++) { + EggPolygon *poly = new EggPolygon; + group->add_child(poly); + poly->copy_attributes(_polys[pi]._attrib); + + if (!poly->has_color() && _appearance._has_material) { + poly->set_color(_appearance._color); + } + + if (_appearance._tex != nullptr) { + poly->set_texture(_appearance._tex); + } + + if (!solid) { + poly->set_bface_flag(true); + } + + if (ccw) { + // The vertices are counterclockwise, same as Egg. + for (int pv = 0; pv < (int)_polys[pi]._verts.size(); pv++) { + EggVertex vert(_polys[pi]._verts[pv]._attrib); + LVertexd pos = + _polys[pi]._verts[pv]._pos * net_transform; + vert.set_pos(pos); + + poly->add_vertex(vpool->create_unique_vertex(vert)); + } + } else { + // The vertices are clockwise, so add 'em in reverse order. + for (int pv = (int)_polys[pi]._verts.size() - 1; pv >= 0; pv--) { + EggVertex vert(_polys[pi]._verts[pv]._attrib); + LVertexd pos = + _polys[pi]._verts[pv]._pos * net_transform; + vert.set_pos(pos); + + poly->add_vertex(vpool->create_unique_vertex(vert)); + } + } + } +} + + +/** + * + */ +void IndexedFaceSet:: +compute_normals(EggGroup *group) { + const VrmlNode *normal = _geometry->get_value("normal")._sfnode._p; + if (normal == nullptr) { + // Compute normals. + double creaseAngle = _geometry->get_value("creaseAngle")._sffloat; + if (creaseAngle == 0.0) { + group->recompute_polygon_normals(); + } else { + group->recompute_vertex_normals(rad_2_deg(creaseAngle)); + } + } +} diff --git a/pandatool/src/vrmlegg/indexedFaceSet.h b/pandatool/src/vrmlegg/indexedFaceSet.h new file mode 100644 index 00000000..48e63ae8 --- /dev/null +++ b/pandatool/src/vrmlegg/indexedFaceSet.h @@ -0,0 +1,83 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file indexedFaceSet.h + * @author drose + * @date 1999-06-24 + */ + +#ifndef INDEXEDFACESET_H +#define INDEXEDFACESET_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "epvector.h" +#include "pset.h" +#include "eggPolygon.h" +#include "eggVertex.h" +#include "eggAttributes.h" + +class VrmlNode; +class EggData; +class EggGroup; +class EggVertexPool; +class VRMLAppearance; +class LMatrix4d; + +/** + * Decodes the vertices and faces in a VRML indexed face set, and creates the + * corresponding egg geometry. + */ +class IndexedFaceSet { +public: + IndexedFaceSet(const VrmlNode *geometry, const VRMLAppearance &appearance); + + void convert_to_egg(EggGroup *group, const LMatrix4d &net_transform); + +private: + void get_coord_values(); + void get_polys(); + void get_vrml_colors(const VrmlNode *color_node, double transparency, + pvector &color_list); + void get_vrml_normals(const VrmlNode *normal_node, + pvector &normal_list); + void get_vrml_uvs(const VrmlNode *texCoord_node, + pvector &uv_list); + + bool get_colors(); + bool get_normals(); + void assign_per_vertex_normals(); + bool get_uvs(); + void assign_per_vertex_uvs(); + void make_polys(EggVertexPool *vpool, EggGroup *group, + const LMatrix4d &net_transform); + void compute_normals(EggGroup *group); + + class VrmlVertex { + public: + int _index; + LVertexd _pos; + EggVertex _attrib; + }; + class VrmlPolygon { + public: + EggPolygon _attrib; + epvector _verts; + }; + pvector _coord_values; + epvector _polys; + pvector _per_vertex_uvs; + pvector _per_vertex_normals; + + bool _has_normals; + + const VrmlNode *_geometry; + const VRMLAppearance &_appearance; +}; + +#endif diff --git a/pandatool/src/vrmlegg/vrmlAppearance.cxx b/pandatool/src/vrmlegg/vrmlAppearance.cxx new file mode 100644 index 00000000..06f9186c --- /dev/null +++ b/pandatool/src/vrmlegg/vrmlAppearance.cxx @@ -0,0 +1,67 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlAppearance.cxx + * @author drose + * @date 1999-06-24 + */ + +#include "vrmlAppearance.h" +#include "vrmlNode.h" +#include "deg_2_rad.h" + +VRMLAppearance:: +VRMLAppearance(const VrmlNode *appearance) { + _has_material = false; + _transparency = 0.0; + _color.set(1.0f, 1.0f, 1.0f, 1.0f); + _has_tex_transform = false; + + if (appearance != nullptr) { + const VrmlNode *material = appearance->get_value("material")._sfnode._p; + if (material != nullptr) { + _has_material = true; + const double *c = material->get_value("diffuseColor")._sfvec; + _transparency = material->get_value("transparency")._sffloat; + _color.set(c[0], c[1], c[2], 1.0 - _transparency); + } + + const VrmlNode *tex_transform = appearance->get_value("textureTransform")._sfnode._p; + if (tex_transform != nullptr) { + if (strcmp(tex_transform->_type->getName(), "TextureTransform") == 0) { + _has_tex_transform = true; + const double *c = tex_transform->get_value("center")._sfvec; + _tex_center.set(c[0], c[1]); + _tex_rotation = tex_transform->get_value("rotation")._sffloat; + const double *s = tex_transform->get_value("scale")._sfvec; + _tex_scale.set(s[0], s[1]); + const double *t = tex_transform->get_value("translation")._sfvec; + _tex_translation.set(t[0], t[1]); + } + } + + const VrmlNode *texture = appearance->get_value("texture")._sfnode._p; + if (texture != nullptr) { + if (strcmp(texture->_type->getName(), "ImageTexture") == 0) { + MFArray *url = texture->get_value("url")._mf; + if (!url->empty()) { + const char *filename = (*url->begin())._sfstring; + _tex = new EggTexture("tref", filename); + + if (_has_tex_transform) { + _tex->add_translate2d(-_tex_center); + _tex->add_scale2d(_tex_scale); + _tex->add_rotate2d(rad_2_deg(_tex_rotation)); + _tex->add_translate2d(_tex_center); + _tex->add_translate2d(_tex_translation); + } + } + } + } + } +} diff --git a/pandatool/src/vrmlegg/vrmlAppearance.h b/pandatool/src/vrmlegg/vrmlAppearance.h new file mode 100644 index 00000000..791d5fb6 --- /dev/null +++ b/pandatool/src/vrmlegg/vrmlAppearance.h @@ -0,0 +1,39 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlAppearance.h + * @author drose + * @date 1999-06-24 + */ + +#ifndef VRMLAPPEARANCE_H +#define VRMLAPPEARANCE_H + +#include "pandatoolbase.h" +#include "eggTexture.h" +#include "pt_EggTexture.h" + +class VrmlNode; + +class VRMLAppearance { +public: + VRMLAppearance(const VrmlNode *vrmlAppearance); + + bool _has_material; + LColor _color; + double _transparency; + PT_EggTexture _tex; + + bool _has_tex_transform; + LVecBase2d _tex_center; + double _tex_rotation; + LVecBase2d _tex_scale; + LVecBase2d _tex_translation; +}; + +#endif diff --git a/pandatool/src/vrmlegg/vrmlToEggConverter.cxx b/pandatool/src/vrmlegg/vrmlToEggConverter.cxx new file mode 100644 index 00000000..d308e347 --- /dev/null +++ b/pandatool/src/vrmlegg/vrmlToEggConverter.cxx @@ -0,0 +1,382 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlToEggConverter.cxx + * @author drose + * @date 2004-10-01 + */ + +#include "vrmlToEggConverter.h" +#include "vrmlAppearance.h" +#include "indexedFaceSet.h" +#include "vrmlNodeType.h" +#include "parse_vrml.h" +#include "vrmlParser.h" +#include "eggGroupNode.h" +#include "eggGroup.h" +#include "eggData.h" +#include "deg_2_rad.h" + +/** + * + */ +VRMLToEggConverter:: +VRMLToEggConverter() { +} + +/** + * + */ +VRMLToEggConverter:: +VRMLToEggConverter(const VRMLToEggConverter ©) : + SomethingToEggConverter(copy) +{ +} + +/** + * + */ +VRMLToEggConverter:: +~VRMLToEggConverter() { +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *VRMLToEggConverter:: +make_copy() { + return new VRMLToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +std::string VRMLToEggConverter:: +get_name() const { + return "VRML"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +std::string VRMLToEggConverter:: +get_extension() const { + return "wrl"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool VRMLToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + */ +bool VRMLToEggConverter:: +convert_file(const Filename &filename) { + clear_error(); + + VrmlScene *scene = parse_vrml(filename); + if (scene == nullptr) { + return false; + } + + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_yup_right); + } + + // First, resolve all the DEFUSE references, and count the number of times + // each node is USEd. + Nodes nodes; + VrmlScene::iterator si; + for (si = scene->begin(); si != scene->end(); ++si) { + get_all_defs((*si)._node, nodes); + } + + // Now go through the hierarchy again, and this time actually build the egg + // structure. + VrmlScene::const_iterator csi; + for (csi = scene->begin(); csi != scene->end(); ++csi) { + vrml_node((*csi)._node, get_egg_data(), LMatrix4d::ident_mat()); + } + + return !had_error(); +} + +/** + * Makes a first pass through the VRML hierarchy, identifying all nodes marked + * with a DEF code, and also counting the times each one is referenced by USE. + * Later, we'll need this information: if a node is referenced at least once, + * we need to define it as an instance node. + */ +void VRMLToEggConverter:: +get_all_defs(SFNodeRef &vrml, VRMLToEggConverter::Nodes &nodes) { + Nodes::iterator ni; + + switch (vrml._type) { + case SFNodeRef::T_def: + // If this is a node definition, add it to the map. + nassertv(vrml._name != nullptr); + nassertv(vrml._p != nullptr); + /* + This happens too often to bother yelling about it. + ni = nodes.find(vrml._name); + if (ni != nodes.end()) { + cerr << "Warning: node name " << vrml._name + << " appears multiple times.\n"; + } + */ + nodes[vrml._name] = vrml._p; + break; + + case SFNodeRef::T_use: + // If it's a reference, resolve it. + nassertv(vrml._name != nullptr); + ni = nodes.find(vrml._name); + if (ni == nodes.end()) { + std::cerr << "Unknown node reference: " << vrml._name << "\n"; + } else { + // Increment the use count of the node. + (*ni).second->_use_count++; + + // Store the pointer itself in the reference, so we don't have to do + // this again later. + vrml._p = (*ni).second; + } + return; + + default: + break; + } + + VrmlNode *node = vrml._p; + if (node != nullptr) { + VrmlNode::Fields::iterator fi; + for (fi = node->_fields.begin(); fi != node->_fields.end(); ++fi) { + if ((*fi)._type->type == SFNODE) { + get_all_defs((*fi)._value._sfnode, nodes); + } else if ((*fi)._type->type == MFNODE) { + MFArray *children = (*fi)._value._mf; + MFArray::iterator ci; + for (ci = children->begin(); ci != children->end(); ++ci) { + get_all_defs((*ci)._sfnode, nodes); + } + } + } + } +} + +/** + * Processes a single VRML node, converting it to egg and adding it to the egg + * file, if appropriate, or doing whatever else should be done. + */ +void VRMLToEggConverter:: +vrml_node(const SFNodeRef &vrml, EggGroupNode *egg, + const LMatrix4d &net_transform) { + const VrmlNode *node = vrml._p; + if (node != nullptr) { + // Now add it to the egg file at this point. + if (strcmp(node->_type->getName(), "Group") == 0) { + vrml_grouping_node(vrml, egg, net_transform, + &VRMLToEggConverter::vrml_group); + } else if (strcmp(node->_type->getName(), "Transform") == 0) { + vrml_grouping_node(vrml, egg, net_transform, + &VRMLToEggConverter::vrml_transform); + } else if (strcmp(node->_type->getName(), "Shape") == 0) { + vrml_grouping_node(vrml, egg, net_transform, + &VRMLToEggConverter::vrml_shape); + } + } +} + +/** + * Begins initial processing of a grouping-type node; that is, any node (like + * Group, Transform, or Shape) that maps to a or in egg. + * This create the group and does any instance-munging necessary, then calls + * the indicated method with the new parameters. + */ +void VRMLToEggConverter:: +vrml_grouping_node(const SFNodeRef &vrml, EggGroupNode *egg, + const LMatrix4d &net_transform, + void (VRMLToEggConverter::*process_func) + (const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform)) { + const VrmlNode *node = vrml._p; + nassertv(node != nullptr); + std::string name; + if (vrml._name != nullptr) { + name = vrml._name; + } + + /* + The following code fragment was used in the old DWD-style egg + library. Currently, the Panda egg library doesn't support + instance references, so we deal with VRML instances by copying. + + if (vrml._type == SFNodeRef::T_use) { + // If this is an instancing reference, just add the reference and return; + // no need for further processing on the node. + Instances::const_iterator fi = _instances.find(node); + assert(fi != _instances.end()); + EggInstance *inst = _data.CreateInstance(egg); + inst->AddGroupRef((*fi).second); + return; + } + */ + + PT(EggGroup) group = new EggGroup(name); + egg->add_child(group); + + LMatrix4d next_transform = net_transform; + + if (node->_use_count > 0) { + // If this node is referenced one or more times later in the file, we must + // make it an instance node. + group->set_group_type(EggGroup::GT_instance); + next_transform = LMatrix4d::ident_mat(); + + // And define the instance for future references. _instances[node] = + // group; + } + + (this->*process_func)(node, group, next_transform); +} + + +/** + * Creates an Egg group corresponding to the VRML group. + */ +void VRMLToEggConverter:: +vrml_group(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform) { + const MFArray *children = node->get_value("children")._mf; + MFArray::const_iterator ci; + for (ci = children->begin(); ci != children->end(); ++ci) { + vrml_node((*ci)._sfnode, group, net_transform); + } +} + +/** + * Creates an Egg group with a transform corresponding to the VRML group. + */ +void VRMLToEggConverter:: +vrml_transform(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform) { + const double *scale = node->get_value("scale")._sfvec; + const double *rotation = node->get_value("rotation")._sfvec; + const double *translation = node->get_value("translation")._sfvec; + + const double *center = node->get_value("center")._sfvec; + const double *o = node->get_value("scaleOrientation")._sfvec; + + LMatrix4d local_transform = LMatrix4d::ident_mat(); + + bool any_transform = false; + + if (scale[0] != 1.0 || scale[1] != 1.0 || scale[2] != 1.0) { + any_transform = true; + if (center[0] != 0.0 || center[1] != 0.0 || center[2] != 0.0) { + local_transform *= + LMatrix4d::translate_mat(-center[0], -center[1], -center[2]); + + if (o[3] != 0.0) { + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(-o[3]), LVector3d(o[0], o[1], o[2])); + local_transform *= + LMatrix4d::scale_mat(scale[0], scale[1], scale[2]); + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(o[3]), LVector3d(o[0], o[1], o[2])); + + } else { + local_transform *= + LMatrix4d::scale_mat(scale[0], scale[1], scale[2]); + } + local_transform *= + LMatrix4d::translate_mat(center[0], center[1], center[2]); + + } else { + if (o[3] != 0.0) { + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(-o[3]), LVector3d(o[0], o[1], o[2])); + local_transform *= + LMatrix4d::scale_mat(scale[0], scale[1], scale[2]); + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(o[3]), LVector3d(o[0], o[1], o[2])); + + } else { + local_transform *= + LMatrix4d::scale_mat(scale[0], scale[1], scale[2]); + } + } + } + + if (rotation[3] != 0.0) { + any_transform = true; + if (center[0] != 0.0 || center[1] != 0.0 || center[2] != 0.0) { + local_transform *= + LMatrix4d::translate_mat(-center[0], -center[1], -center[2]); + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(rotation[3]), + LVector3d(rotation[0], rotation[1], rotation[2])); + local_transform *= + LMatrix4d::translate_mat(center[0], center[1], center[2]); + + } else { + local_transform *= + LMatrix4d::rotate_mat(rad_2_deg(rotation[3]), + LVector3d(rotation[0], rotation[1], rotation[2])); + } + } + + if (translation[0] != 0.0 || + translation[1] != 0.0 || + translation[2] != 0.0) { + any_transform = true; + local_transform *= + LMatrix4d::translate_mat(translation[0], translation[1], translation[2]); + } + + if (any_transform) { + group->set_transform3d(local_transform); + } + + LMatrix4d next_transform = local_transform * net_transform; + + const MFArray *children = node->get_value("children")._mf; + MFArray::const_iterator ci; + for (ci = children->begin(); ci != children->end(); ++ci) { + vrml_node((*ci)._sfnode, group, next_transform); + } +} + +/** + * Creates an Egg group corresponding a VRML shape. This will probably + * contain a vertex pool and a number of polygons. + */ +void VRMLToEggConverter:: +vrml_shape(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform) { + const VrmlNode *geometry = node->get_value("geometry")._sfnode._p; + + if (geometry != nullptr) { + VRMLAppearance appearance(node->get_value("appearance")._sfnode._p); + + if (strcmp(geometry->_type->getName(), "IndexedFaceSet") == 0) { + IndexedFaceSet ifs(geometry, appearance); + ifs.convert_to_egg(group, net_transform); + } else { + std::cerr << "Ignoring " << geometry->_type->getName() << "\n"; + } + } +} diff --git a/pandatool/src/vrmlegg/vrmlToEggConverter.h b/pandatool/src/vrmlegg/vrmlToEggConverter.h new file mode 100644 index 00000000..34a9b684 --- /dev/null +++ b/pandatool/src/vrmlegg/vrmlToEggConverter.h @@ -0,0 +1,66 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlToEggConverter.h + * @author drose + * @date 2004-10-01 + */ + +#ifndef VRMLTOEGGCONVERTER_H +#define VRMLTOEGGCONVERTER_H + +#include "pandatoolbase.h" + +#include "somethingToEggConverter.h" +#include "pmap.h" + +class VrmlNode; +struct SFNodeRef; +class EggGroupNode; +class EggGroup; +class LMatrix4d; + +/** + * This class supervises the construction of an EggData structure from a VRML + * file. + */ +class VRMLToEggConverter : public SomethingToEggConverter { +public: + VRMLToEggConverter(); + VRMLToEggConverter(const VRMLToEggConverter ©); + ~VRMLToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + + virtual bool convert_file(const Filename &filename); + +private: + typedef pmap Nodes; + + void get_all_defs(SFNodeRef &vrml, Nodes &nodes); + void vrml_node(const SFNodeRef &vrml, EggGroupNode *egg, + const LMatrix4d &net_transform); + + void vrml_grouping_node(const SFNodeRef &vrml, EggGroupNode *egg, + const LMatrix4d &net_transform, + void (VRMLToEggConverter::*process_func) + (const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform)); + void vrml_group(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform); + void vrml_transform(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform); + void vrml_shape(const VrmlNode *node, EggGroup *group, + const LMatrix4d &net_transform); +}; + +#endif diff --git a/pandatool/src/vrmlprogs/CMakeLists.txt b/pandatool/src/vrmlprogs/CMakeLists.txt new file mode 100644 index 00000000..9f92833c --- /dev/null +++ b/pandatool/src/vrmlprogs/CMakeLists.txt @@ -0,0 +1,15 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(vrml-trans vrmlTrans.cxx vrmlTrans.h) +target_link_libraries(vrml-trans p3progbase p3vrml) +install(TARGETS vrml-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(vrml2egg vrmlToEgg.cxx vrmlToEgg.h) + target_link_libraries(vrml2egg p3vrmlegg p3eggbase p3progbase) + install(TARGETS vrml2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/vrmlprogs/vrmlToEgg.cxx b/pandatool/src/vrmlprogs/vrmlToEgg.cxx new file mode 100644 index 00000000..dee6b5eb --- /dev/null +++ b/pandatool/src/vrmlprogs/vrmlToEgg.cxx @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlToEgg.cxx + * @author drose + * @date 2004-10-01 + */ + +#include "vrmlToEgg.h" + +#include "vrmlToEggConverter.h" + +/** + * + */ +VRMLToEgg:: +VRMLToEgg() : + SomethingToEgg("VRML", ".wrl") +{ + add_units_options(); + add_normals_options(); + add_transform_options(); + + set_program_brief("convert VRML 2.0 model files to .egg"); + set_program_description + ("This program converts VRML 2.0 model files to egg. Animated files, " + "and VRML 1.0 files, are not supported."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is y-up."); + + _coordinate_system = CS_yup_right; +} + +/** + * + */ +void VRMLToEgg:: +run() { + nout << "Reading " << _input_filename << "\n"; + + _data->set_coordinate_system(_coordinate_system); + + VRMLToEggConverter converter; + converter.set_egg_data(_data); + converter._allow_errors = _allow_errors; + + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Errors in conversion.\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + VRMLToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/vrmlprogs/vrmlToEgg.h b/pandatool/src/vrmlprogs/vrmlToEgg.h new file mode 100644 index 00000000..90edb3b1 --- /dev/null +++ b/pandatool/src/vrmlprogs/vrmlToEgg.h @@ -0,0 +1,32 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlToEgg.h + * @author drose + * @date 2004-10-01 + */ + +#ifndef VRMLTOEGG_H +#define VRMLTOEGG_H + +#include "pandatoolbase.h" + +#include "somethingToEgg.h" +#include "vrmlToEggConverter.h" + +/** + * A program to read a VRML file and generate an egg file. + */ +class VRMLToEgg : public SomethingToEgg { +public: + VRMLToEgg(); + + void run(); +}; + +#endif diff --git a/pandatool/src/vrmlprogs/vrmlTrans.cxx b/pandatool/src/vrmlprogs/vrmlTrans.cxx new file mode 100644 index 00000000..48ed508b --- /dev/null +++ b/pandatool/src/vrmlprogs/vrmlTrans.cxx @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlTrans.cxx + * @author drose + * @date 2004-10-01 + */ + +#include "vrmlTrans.h" +#include "parse_vrml.h" + +/** + * + */ +VRMLTrans:: +VRMLTrans() : + WithOutputFile(true, true, false) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = ".wrl"; + + set_program_brief("reads and writes VRML 2.0 files"); + set_program_description + ("This program reads a VRML 2.0 file (.wrl) and writes an " + "essentially equivalent .wrl file. It is primarily useful for " + "debugging the VRML parser that is part of the Pandatool library."); + + clear_runlines(); + add_runline("[opts] input.wrl > output.wrl"); + add_runline("[opts] input.wrl output.wrl"); + add_runline("[opts] -o output.wrl input.wrl"); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting .wrl file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file.", + &VRMLTrans::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +/** + * + */ +void VRMLTrans:: +run() { + nout << "Reading " << _input_filename << "\n"; + + VrmlScene *scene = parse_vrml(_input_filename); + if (scene == nullptr) { + nout << "Unable to read.\n"; + exit(1); + } + + get_output() << *scene << "\n"; +} + + +/** + * + */ +bool VRMLTrans:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + if (args.empty()) { + nout << "You must specify the .wrl file to read on the command line.\n"; + return false; + + } else if (args.size() != 1) { + nout << "You must specify only one .wrl file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int main(int argc, char *argv[]) { + VRMLTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/vrmlprogs/vrmlTrans.h b/pandatool/src/vrmlprogs/vrmlTrans.h new file mode 100644 index 00000000..4ff49d24 --- /dev/null +++ b/pandatool/src/vrmlprogs/vrmlTrans.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file vrmlTrans.h + * @author drose + * @date 2004-10-01 + */ + +#ifndef VRMLTRANS_H +#define VRMLTRANS_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "withOutputFile.h" + +/** + * A program to read a VRML file and output an essentially similar VRML file. + * This is mainly useful to test the VRML parser used in Panda. + */ +class VRMLTrans : public ProgramBase, public WithOutputFile { +public: + VRMLTrans(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; +}; + +#endif diff --git a/pandatool/src/win-stats/CMakeLists.txt b/pandatool/src/win-stats/CMakeLists.txt new file mode 100644 index 00000000..8c5a0a0f --- /dev/null +++ b/pandatool/src/win-stats/CMakeLists.txt @@ -0,0 +1,45 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +if(NOT WIN32 OR NOT HAVE_NET) + return() +endif() + +set(WINSTATS_HEADERS + winStatsChartMenu.h + winStatsFlameGraph.h + winStatsGraph.h + winStats.h + winStatsLabel.h winStatsLabel.I + winStatsLabelStack.h + winStatsMenuId.h + winStatsMonitor.h winStatsMonitor.I + winStatsPianoRoll.h + winStatsServer.h + winStatsStripChart.h + winStatsTimeline.h +) + +set(WINSTATS_SOURCES + winStatsChartMenu.cxx + winStats.cxx + winStatsFlameGraph.cxx + winStatsGraph.cxx + winStatsLabel.cxx + winStatsLabelStack.cxx + winStatsMonitor.cxx + winStatsPianoRoll.cxx + winStatsServer.cxx + winStatsStripChart.cxx + winStatsTimeline.cxx +) + +composite_sources(win-stats WINSTATS_SOURCES) +add_executable(win-stats ${WINSTATS_HEADERS} ${WINSTATS_SOURCES}) +target_link_libraries(win-stats p3progbase p3pstatserver comctl32.lib uxtheme.lib) + +# This program is NOT actually called win-stats. It's just pstats.exe +set_target_properties(win-stats PROPERTIES OUTPUT_NAME "pstats") + +install(TARGETS win-stats EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/pandatool/src/win-stats/winStats.cxx b/pandatool/src/win-stats/winStats.cxx new file mode 100644 index 00000000..a98d2063 --- /dev/null +++ b/pandatool/src/win-stats/winStats.cxx @@ -0,0 +1,66 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStats.cxx + * @author drose + * @date 2003-12-02 + */ + +#include "pandatoolbase.h" + +#include "winStatsServer.h" +#include "config_pstatclient.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include + +// Enable common controls version 6, necessary for modern visual styles +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +int main(int argc, char *argv[]) { + // Initialize commctl32.dll. + INITCOMMONCONTROLSEX icc; + icc.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES; + icc.dwSize = sizeof(INITCOMMONCONTROLSEX); + InitCommonControlsEx(&icc); + + // Signal DPI awareness. + SetProcessDPIAware(); + + // Create the server window. + WinStatsServer *server = new WinStatsServer; + if (server->parse_command_line(argc, argv, false) == ProgramBase::EC_failure) { + MessageBox(nullptr, "Failed to parse command-line options.", + "PStats Error", MB_OK | MB_ICONEXCLAMATION); + return 1; + } + + // Now get lost in the Windows message loop. + MSG msg; + int retval; + retval = GetMessage(&msg, nullptr, 0, 0); + while (retval != 0) { + if (retval == -1) { + nout << "Error processing message queue.\n"; + exit(1); + } + TranslateMessage(&msg); + DispatchMessage(&msg); + retval = GetMessage(&msg, nullptr, 0, 0); + } + + return 0; +} + +int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { + return main(__argc, __argv); +} diff --git a/pandatool/src/win-stats/winStats.h b/pandatool/src/win-stats/winStats.h new file mode 100644 index 00000000..a1d9ed5e --- /dev/null +++ b/pandatool/src/win-stats/winStats.h @@ -0,0 +1,19 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStats.h + * @author drose + * @date 2003-12-02 + */ + +#ifndef WINSTATS_H +#define WINSTATS_H + +#include "pStatServer.h" + +#endif diff --git a/pandatool/src/win-stats/winStatsChartMenu.cxx b/pandatool/src/win-stats/winStatsChartMenu.cxx new file mode 100644 index 00000000..3ee9faf6 --- /dev/null +++ b/pandatool/src/win-stats/winStatsChartMenu.cxx @@ -0,0 +1,279 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsChartMenu.cxx + * @author drose + * @date 2004-01-08 + */ + +#include "winStatsChartMenu.h" +#include "winStatsMenuId.h" +#include "winStatsMonitor.h" + +/** + * + */ +WinStatsChartMenu:: +WinStatsChartMenu(WinStatsMonitor *monitor, int thread_index) : + _monitor(monitor), + _thread_index(thread_index) +{ + _menu = CreatePopupMenu(); + do_update(); +} + +/** + * + */ +WinStatsChartMenu:: +~WinStatsChartMenu() { + DestroyMenu(_menu); +} + +/** + * Returns the Windows menu handle for this particular menu. + */ +HMENU WinStatsChartMenu:: +get_menu_handle() { + return _menu; +} + +/** + * Adds the menu to the end of the indicated menu bar. + */ +void WinStatsChartMenu:: +add_to_menu_bar(HMENU menu_bar, int before_menu_id) { + const PStatClientData *client_data = _monitor->get_client_data(); + std::string thread_name; + if (_thread_index == 0) { + // A special case for the main thread. + thread_name = "Graphs"; + } else { + thread_name = client_data->get_thread_name(_thread_index); + } + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = 1000 | _thread_index; + mii.hSubMenu = _menu; + mii.dwTypeData = (char *)thread_name.c_str(); + InsertMenuItem(menu_bar, before_menu_id, FALSE, &mii); +} + +/** + * + */ +void WinStatsChartMenu:: +remove_from_menu_bar(HMENU menu_bar) { + RemoveMenu(menu_bar, 1000 | _thread_index, MF_BYCOMMAND); +} + +/** + * Checks to see if the menu needs to be updated (e.g. because of new data + * from the client), and updates it if necessary. + */ +void WinStatsChartMenu:: +check_update() { + PStatView &view = _monitor->get_view(_thread_index); + if (view.get_level_index() != _last_level_index) { + do_update(); + } +} + +/** + * Unconditionally updates the menu with the latest data from the client. + */ +void WinStatsChartMenu:: +do_update() { + PStatView &view = _monitor->get_view(_thread_index); + _last_level_index = view.get_level_index(); + + // First, remove all of the old entries from the menu. + int num_items = GetMenuItemCount(_menu); + for (int i = num_items - 1; i >= 0; i--) { + DeleteMenu(_menu, i, MF_BYPOSITION); + } + + // Now rebuild the menu with the new set of entries. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + if (_thread_index == 0) { + // Timeline goes first. + { + WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_timeline, false); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Timeline"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } + + // And the piano roll (even though it's not very useful nowadays) + { + WinStatsMonitor::MenuDef menu_def(_thread_index, -1, WinStatsMonitor::CT_piano_roll, false); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Piano Roll"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } + + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } + + // The menu item(s) for the thread's frame time goes second. + add_view(_menu, view.get_top_level(), false); + + bool needs_separator = true; + + // And then the menu item(s) for each of the level values. + const PStatClientData *client_data = _monitor->get_client_data(); + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, _thread_index)) { + + // We put a separator between the above frame collector and the first + // level collector. + if (needs_separator) { + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + needs_separator = false; + } + + PStatView &level_view = _monitor->get_level_view(collector, _thread_index); + add_view(_menu, level_view.get_top_level(), true); + } + } + + // For the main thread menu, also some options relating to all graph windows. + if (_thread_index == 0) { + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = MI_graphs_close_all; + mii.dwTypeData = "Close All Graphs"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = MI_graphs_reopen_default; + mii.dwTypeData = "Reopen Default Graphs"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = MI_graphs_save_default; + mii.dwTypeData = "Save Current Layout as Default"; + InsertMenuItem(_menu, GetMenuItemCount(_menu), TRUE, &mii); + } +} + +/** + * Adds a new entry or entries to the menu for the indicated view and its + * children. + */ +void WinStatsChartMenu:: +add_view(HMENU parent_menu, const PStatViewLevel *view_level, bool show_level) { + int collector = view_level->get_collector(); + + const PStatClientData *client_data = _monitor->get_client_data(); + std::string collector_name = client_data->get_collector_name(collector); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + int num_children = view_level->get_num_children(); + if (show_level && num_children == 0) { + // For a level collector without children, no point in making a submenu. + WinStatsMonitor::MenuDef menu_def(_thread_index, collector, WinStatsMonitor::CT_strip_chart, show_level); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = (char *)collector_name.c_str(); + InsertMenuItem(parent_menu, GetMenuItemCount(parent_menu), TRUE, &mii); + return; + } + + HMENU menu; + if (!show_level && collector == 0 && num_children == 0) { + // Root collector without children, just add the options directly to the + // parent menu. + menu = parent_menu; + } + else { + // Create a submenu. + menu = CreatePopupMenu(); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = menu; + mii.dwTypeData = (char *)collector_name.c_str(); + InsertMenuItem(parent_menu, GetMenuItemCount(parent_menu), TRUE, &mii); + } + + { + WinStatsMonitor::MenuDef menu_def(_thread_index, collector, WinStatsMonitor::CT_strip_chart, show_level); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Open Strip Chart"; + InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &mii); + } + + if (!show_level) { + if (collector == 0 && num_children == 0) { + collector = -1; + } + + WinStatsMonitor::MenuDef menu_def(_thread_index, collector, WinStatsMonitor::CT_flame_graph); + int menu_id = _monitor->get_menu_id(menu_def); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING; + mii.wID = menu_id; + mii.dwTypeData = "Open Flame Graph"; + InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &mii); + } + + if (num_children > 0) { + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &mii); + + // Reverse the order since the menus are listed from the top down; we want + // to be visually consistent with the graphs, which list these labels from + // the bottom up. + for (int c = num_children - 1; c >= 0; c--) { + add_view(menu, view_level->get_child(c), show_level); + } + } +} diff --git a/pandatool/src/win-stats/winStatsChartMenu.h b/pandatool/src/win-stats/winStatsChartMenu.h new file mode 100644 index 00000000..c11553a1 --- /dev/null +++ b/pandatool/src/win-stats/winStatsChartMenu.h @@ -0,0 +1,56 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsChartMenu.h + * @author drose + * @date 2004-01-08 + */ + +#ifndef WINSTATSCHARTMENU_H +#define WINSTATSCHARTMENU_H + +#include "pandatoolbase.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsMonitor; +class PStatView; +class PStatViewLevel; + +/** + * A pulldown menu of charts available for a particular thread. + */ +class WinStatsChartMenu { +public: + WinStatsChartMenu(WinStatsMonitor *monitor, int thread_index); + ~WinStatsChartMenu(); + + int get_thread_index() const { return _thread_index; } + + HMENU get_menu_handle(); + void add_to_menu_bar(HMENU menu_bar, int before_menu_id); + void remove_from_menu_bar(HMENU menu_bar); + + void check_update(); + void do_update(); + +private: + void add_view(HMENU parent_menu, const PStatViewLevel *view_level, + bool show_level); + + WinStatsMonitor *_monitor; + int _thread_index; + + int _last_level_index; + HMENU _menu; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsFlameGraph.cxx b/pandatool/src/win-stats/winStatsFlameGraph.cxx new file mode 100644 index 00000000..9c4d2f67 --- /dev/null +++ b/pandatool/src/win-stats/winStatsFlameGraph.cxx @@ -0,0 +1,868 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsFlameGraph.cxx + * @author rdb + * @date 2022-01-28 + */ + +#include "winStatsFlameGraph.h" +#include "winStatsLabel.h" +#include "winStatsMonitor.h" +#include "pStatCollectorDef.h" + +#include + +static const int default_flame_graph_width = 1085; +static const int default_flame_graph_height = 210; + +bool WinStatsFlameGraph::_window_class_registered = false; +const char * const WinStatsFlameGraph::_window_class_name = "flame"; + +/** + * + */ +WinStatsFlameGraph:: +WinStatsFlameGraph(WinStatsMonitor *monitor, int thread_index, + int collector_index, int frame_number) : + PStatFlameGraph(monitor, + thread_index, collector_index, frame_number, + monitor->get_pixel_scale() * default_flame_graph_width / 4, + monitor->get_pixel_scale() * default_flame_graph_height / 4), + WinStatsGraph(monitor) +{ + _left_margin = _pixel_scale * 2; + _right_margin = _pixel_scale * 2; + _top_margin = _pixel_scale * 6; + _bottom_margin = _pixel_scale * 2; + + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + _average_check_box = 0; + + create_window(); + force_redraw(); +} + +/** + * + */ +WinStatsFlameGraph:: +~WinStatsFlameGraph() { +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void WinStatsFlameGraph:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } + + if (!_pause) { + update(); + + std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name()); + if (_net_value_text != text) { + _net_value_text = text; + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void WinStatsFlameGraph:: +force_redraw() { + PStatFlameGraph::force_redraw(); +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void WinStatsFlameGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatFlameGraph::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void WinStatsFlameGraph:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void WinStatsFlameGraph:: +on_click_label(int collector_index) { + if (collector_index != get_collector_index()) { + if (collector_index == -1) { + clear_history(); + set_collector_index(-1); + } else { + push_collector_index(collector_index); + } + + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } + } +} + +/** + * Called when the user hovers the mouse over a label. + */ +void WinStatsFlameGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + clear_graph_tooltip(); + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void WinStatsFlameGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + + if (!get_average_mode()) { + PStatFlameGraph::force_redraw(); + } + } +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void WinStatsFlameGraph:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + int num_bars = get_xsize() / (_pixel_scale * 25); + + _guide_bars.clear(); + + double dist = get_horizontal_scale() / num_bars; + + for (int i = 1; i < num_bars; ++i) { + _guide_bars.push_back(make_guide_bar(i * dist)); + } + + _guide_bars_changed = true; + + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); +} + +/** + * Erases the chart area. + */ +void WinStatsFlameGraph:: +clear_region() { + RECT rect = { 0, 0, get_xsize(), get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void WinStatsFlameGraph:: +begin_draw() { + clear_region(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_bitmap_dc, get_guide_bar(i)); + } + + SelectObject(_bitmap_dc, WinStatsGraph::_monitor->get_font()); + SelectObject(_bitmap_dc, GetStockObject(NULL_PEN)); + SetBkMode(_bitmap_dc, TRANSPARENT); + SetTextAlign(_bitmap_dc, TA_LEFT | TA_TOP | TA_NOUPDATECP); +} + +/** + * Should be overridden by the user class. Should draw a single bar at the + * indicated location. + */ +void WinStatsFlameGraph:: +draw_bar(int depth, int from_x, int to_x, int collector_index, int parent_index) { + int bottom = get_ysize() - 1 - depth * _pixel_scale * 5; + int top = bottom - _pixel_scale * 5; + + bool is_highlighted = collector_index == _highlighted_index; + HBRUSH brush = get_collector_brush(collector_index, is_highlighted); + + if (to_x < from_x + 2) { + // It's just a tiny sliver. This is a more reliable way to draw it. + RECT rect = {from_x, top + 1, from_x + 1, bottom - 1}; + FillRect(_bitmap_dc, &rect, brush); + } + else { + SelectObject(_bitmap_dc, brush); + RoundRect(_bitmap_dc, + std::max(from_x, -_pixel_scale - 1), + top, + std::min(std::max(to_x, from_x + 1), get_xsize() + _pixel_scale), + bottom, + _pixel_scale, + _pixel_scale); + + int left = std::max(from_x, 0) + _pixel_scale / 2; + int right = std::min(to_x, get_xsize()) - _pixel_scale / 2; + + if ((to_x - from_x) >= _pixel_scale * 4) { + // Only bother drawing the text if we've got some space to draw on. + // Choose a suitable foreground color. + SetTextColor(_bitmap_dc, get_collector_text_color(collector_index, is_highlighted)); + + const PStatClientData *client_data = WinStatsGraph::_monitor->get_client_data(); + const PStatCollectorDef &def = client_data->get_collector_def(collector_index); + + SIZE size; + GetTextExtentPoint32(_bitmap_dc, def._name.data(), def._name.size(), &size); + + if (size.cx < right - left) { + // We have room for more. Show the collector's actual parent, if it's + // different than the block it's shown above. + if (def._parent_index > 0 && def._parent_index != parent_index) { + const PStatCollectorDef &parent_def = client_data->get_collector_def(def._parent_index); + std::string long_name = parent_def._name + ":" + def._name; + + SIZE long_size; + GetTextExtentPoint32(_bitmap_dc, long_name.data(), long_name.size(), &long_size); + if (long_size.cx < right - left) { + TextOut(_bitmap_dc, left, top + (bottom - top - long_size.cy) / 2, + long_name.data(), long_name.length()); + return; + } + } + TextOut(_bitmap_dc, left, top + (bottom - top - size.cy) / 2, + def._name.data(), def._name.length()); + } else { + // Let Windows figure out how to fit it, with ellipsis if necessary. + RECT rect = {left, top, right, bottom}; + DrawText(_bitmap_dc, def._name.data(), def._name.size(), + &rect, DT_LEFT | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER); + } + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void WinStatsFlameGraph:: +end_draw() { + InvalidateRect(_graph_window, nullptr, FALSE); +} + +/** + * Called at the end of the draw cycle. + */ +void WinStatsFlameGraph:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool WinStatsFlameGraph:: +animate(double time, double dt) { + return PStatFlameGraph::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool WinStatsFlameGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsFlameGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); + + // Set the state of the checkbox. + SendMessage(_average_check_box, BM_SETCHECK, get_average_mode() ? BST_CHECKED : BST_UNCHECKED, 0); +} + +/** + * + */ +LONG WinStatsFlameGraph:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + SetFocus(_window); + if (_potential_drag_mode == DM_new_guide_bar) { + set_drag_mode(DM_new_guide_bar); + SetCapture(_graph_window); + return 0; + } + break; + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case BN_CLICKED: + if ((HWND)lparam == _average_check_box) { + int result = SendMessage(_average_check_box, BM_GETCHECK, 0, 0); + if (result == BST_CHECKED) { + set_average_mode(true); + start_animation(); + } else { + set_average_mode(false); + } + return 0; + } + break; + + case 101: + on_click_label(_popup_index); + return 0; + + case 102: + WinStatsGraph::_monitor->open_strip_chart(get_thread_index(), _popup_index, false); + return 0; + + case 103: + WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); + return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; + } + break; + + case WM_KEYDOWN: + { + bool changed = false; + switch (wparam) { + case VK_LEFT: + changed = prev_frame(); + break; + case VK_RIGHT: + changed = next_frame(); + break; + case VK_HOME: + changed = first_frame(); + break; + case VK_END: + changed = last_frame(); + break; + } + if (changed) { + std::string window_title = get_title_text(); + SetWindowText(_window, window_title.c_str()); + return 0; + } + } + break; + + case WM_SYSKEYDOWN: + if (((lparam >> 16) & KF_ALTDOWN) != 0 && wparam == VK_LEFT) { + if (pop_collector_index()) { + std::string window_title = get_title_text(); + SetWindowText(_window, window_title.c_str()); + } + return 0; + } + break; + + case WM_APPCOMMAND: + if (GET_APPCOMMAND_LPARAM(lparam) == APPCOMMAND_BROWSER_BACKWARD) { + if (pop_collector_index()) { + std::string window_title = get_title_text(); + SetWindowText(_window, window_title.c_str()); + } + return TRUE; + } + break; + + default: + break; + } + + return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +LONG WinStatsFlameGraph:: +graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + SetFocus(_window); + if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + int16_t x = LOWORD(lparam); + _drag_start_x = x; + SetCapture(_graph_window); + return 0; + } + break; + + case WM_MOUSEMOVE: + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int x = LOWORD(lparam); + int y = HIWORD(lparam); + + int collector_index = get_bar_collector(pixel_to_depth(y), x); + on_enter_label(collector_index); + + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + } + else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + int16_t x = LOWORD(lparam); + if (x >= 0 && x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(x)); + return 0; + } + } + else if (_drag_mode == DM_guide_bar) { + int16_t x = LOWORD(lparam); + move_user_guide_bar(_drag_guide_bar, pixel_to_height(x)); + return 0; + } + break; + + case WM_MOUSELEAVE: + // When the mouse leaves the graph, stop highlighting. + if (_highlighted_index != -1) { + on_leave_label(_highlighted_index); + } + break; + + case WM_LBUTTONUP: + if (_drag_mode == DM_guide_bar) { + int16_t x = LOWORD(lparam); + if (x < 0 || x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(x)); + } + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + } + break; + + case WM_LBUTTONDBLCLK: + { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + on_click_label(get_bar_collector(pixel_to_depth(y), x)); + return 0; + } + break; + + case WM_CONTEXTMENU: + { + POINT point; + if (GetCursorPos(&point)) { + POINT graph_point = point; + if (ScreenToClient(_graph_window, &graph_point)) { + int depth = pixel_to_depth(graph_point.y); + int collector_index = get_bar_collector(depth, graph_point.x); + if (collector_index >= 0) { + _popup_index = collector_index; + HMENU popup = CreatePopupMenu(); + + std::string label = get_bar_tooltip(depth, graph_point.x); + if (!label.empty()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 0, label.c_str()); + } + if (collector_index == get_collector_index()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 101, "Set as Focus"); + } else { + AppendMenu(popup, MF_STRING, 101, "Set as Focus"); + } + AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); + AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); + TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); + } + } + } + return 0; + } + break; + + case WM_MOUSEHWHEEL: + { + int delta = GET_WHEEL_DELTA_WPARAM(wparam); + if (delta != 0) { + if (delta > 0 ? next_frame() : prev_frame()) { + std::string window_title = get_title_text(); + SetWindowText(_window, window_title.c_str()); + } + } + return 0; + } + break; + + default: + break; + } + + return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsFlameGraph:: +additional_window_paint(HDC hdc) { + // Draw in the labels for the guide bars. + SelectObject(hdc, WinStatsGraph::_monitor->get_font()); + SetTextAlign(hdc, TA_LEFT | TA_BOTTOM); + SetBkMode(hdc, TRANSPARENT); + + int y = _top_margin - _pixel_scale / 2; + + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + draw_guide_label(hdc, y, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + draw_guide_label(hdc, y, get_user_guide_bar(i)); + } + + RECT rect; + GetClientRect(_window, &rect); + + // Now draw the "net value" label at the top. + SetTextAlign(hdc, TA_RIGHT | TA_BOTTOM); + SetTextColor(hdc, RGB(0, 0, 0)); + TextOut(hdc, rect.right - _right_margin, y, + _net_value_text.data(), _net_value_text.length()); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsFlameGraph:: +additional_graph_window_paint(HDC hdc) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(hdc, get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsFlameGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return get_bar_tooltip(pixel_to_depth(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +WinStatsGraph::DragMode WinStatsFlameGraph:: +consider_drag_start(int mouse_x, int mouse_y, int width, int height) { + if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) { + if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = mouse_x - _graph_left; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else if (mouse_x < _left_margin - 2 || + mouse_x > width - _right_margin + 2) { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + // Don't upcall; there's no point resizing the margins. + return DM_none; +} + +/** + * Repositions the graph child window within the parent window according to + * the _margin variables. + */ +void WinStatsFlameGraph:: +move_graph_window(int graph_left, int graph_top, int graph_xsize, int graph_ysize) { + WinStatsGraph::move_graph_window(graph_left, graph_top, graph_xsize, graph_ysize); + if (_average_check_box != 0) { + SIZE size; + SendMessage(_average_check_box, BCM_GETIDEALSIZE, 0, (LPARAM)&size); + + SetWindowPos(_average_check_box, 0, + _left_margin, _top_margin - size.cy - _pixel_scale / 2, + size.cx, size.cy, + SWP_NOZORDER | SWP_SHOWWINDOW); + InvalidateRect(_average_check_box, nullptr, TRUE); + } +} + +/** + * Converts a pixel to a depth index. + */ +int WinStatsFlameGraph:: +pixel_to_depth(int y) const { + return (get_ysize() - 1 - y) / (_pixel_scale * 5); +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void WinStatsFlameGraph:: +draw_guide_bar(HDC hdc, const PStatGraph::GuideBar &bar) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to either edge. + switch (bar._style) { + case GBS_target: + SelectObject(hdc, _light_pen); + break; + + case GBS_user: + SelectObject(hdc, _user_guide_bar_pen); + break; + + case GBS_normal: + SelectObject(hdc, _dark_pen); + break; + } + MoveToEx(hdc, x, 0, nullptr); + LineTo(hdc, x, get_ysize()); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void WinStatsFlameGraph:: +draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar) { + switch (bar._style) { + case GBS_target: + SetTextColor(hdc, _light_color); + break; + + case GBS_user: + SetTextColor(hdc, _user_guide_bar_color); + break; + + case GBS_normal: + SetTextColor(hdc, _dark_color); + break; + } + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + SIZE size; + GetTextExtentPoint32(hdc, label.data(), label.length(), &size); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - size.cx); + double to_height = pixel_to_height(x + size.cx); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + return; + } + } + + int this_x = _graph_left + x - size.cx / 2; + if (x >= 0 && x < get_xsize()) { + TextOut(hdc, this_x, y, + label.data(), label.length()); + } +} + +/** + * Creates the window for this strip chart. + */ +void WinStatsFlameGraph:: +create_window() { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + std::string window_title = get_title_text(); + POINT window_pos = WinStatsGraph::_monitor->get_new_window_pos(); + + RECT win_rect = { + 0, 0, + _left_margin + get_xsize() + _right_margin, + _top_margin + get_ysize() + _bottom_margin + }; + + // compute window size based on desired client area size + AdjustWindowRect(&win_rect, graph_window_style, FALSE); + + _window = + CreateWindowEx(WS_EX_DLGMODALFRAME, _window_class_name, + window_title.c_str(), graph_window_style, + window_pos.x, window_pos.y, + win_rect.right - win_rect.left, + win_rect.bottom - win_rect.top, + WinStatsGraph::_monitor->get_window(), + nullptr, application, 0); + if (!_window) { + nout << "Could not create FlameGraph window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + + _average_check_box = + CreateWindow(WC_BUTTON, "Average", WS_CHILD | BS_AUTOCHECKBOX, + 0, 0, 0, 0, + _window, nullptr, application, 0); + SendMessage(_average_check_box, WM_SETFONT, + (WPARAM)WinStatsGraph::_monitor->get_font(), TRUE); + + if (get_average_mode()) { + SendMessage(_average_check_box, BM_SETCHECK, BST_CHECKED, 0); + start_animation(); + } + + // Ensure that the window is on top of the stack. + SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + SetFocus(_window); +} + +/** + * Registers the window class for the FlameGraph window, if it has not already + * been registered. + */ +void WinStatsFlameGraph:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsFlameGraph *); + + if (!RegisterClass(&wc)) { + nout << "Could not register FlameGraph window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsFlameGraph:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsFlameGraph *self = (WinStatsFlameGraph *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} diff --git a/pandatool/src/win-stats/winStatsFlameGraph.h b/pandatool/src/win-stats/winStatsFlameGraph.h new file mode 100644 index 00000000..6edae514 --- /dev/null +++ b/pandatool/src/win-stats/winStatsFlameGraph.h @@ -0,0 +1,87 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsFlameGraph.h + * @author rdb + * @date 2022-01-28 + */ + +#ifndef WINSTATSFLAMEGRAPH_H +#define WINSTATSFLAMEGRAPH_H + +#include "pandatoolbase.h" + +#include "winStatsGraph.h" +#include "pStatFlameGraph.h" + +class WinStatsLabel; + +/** + * A window that draws a flame chart, which shows the collectors explicitly + * stopping and starting, one frame at a time. + */ +class WinStatsFlameGraph : public PStatFlameGraph, public WinStatsGraph { +public: + WinStatsFlameGraph(WinStatsMonitor *monitor, int thread_index, + int collector_index=-1, int frame_number=-1); + virtual ~WinStatsFlameGraph(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + +protected: + virtual void normal_guide_bars(); + + void clear_region(); + virtual void begin_draw(); + virtual void draw_bar(int depth, int from_x, int to_x, + int collector_index, int parent_index); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual void additional_window_paint(HDC hdc); + virtual void additional_graph_window_paint(HDC hdc); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int mouse_x, int mouse_y, + int width, int height); + virtual void move_graph_window(int graph_left, int graph_top, + int graph_xsize, int graph_ysize); + +private: + int pixel_to_depth(int y) const; + void draw_guide_bar(HDC hdc, const GuideBar &bar); + void draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar); + void create_window(); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + std::string _net_value_text; + HWND _average_check_box; + int _popup_index = -1; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsGraph.cxx b/pandatool/src/win-stats/winStatsGraph.cxx new file mode 100644 index 00000000..d8d41187 --- /dev/null +++ b/pandatool/src/win-stats/winStatsGraph.cxx @@ -0,0 +1,831 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsGraph.cxx + * @author drose + * @date 2003-12-03 + */ + +#include "winStatsGraph.h" +#include "winStatsMonitor.h" +#include "winStatsLabelStack.h" +#include "winStatsServer.h" +#include "trueClock.h" +#include "convert_srgb.h" + +#include + +#define IDC_GRAPH 100 + +DWORD WinStatsGraph::graph_window_style = + WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW | WS_VISIBLE; + +/** + * + */ +WinStatsGraph:: +WinStatsGraph(WinStatsMonitor *monitor) : + _monitor(monitor) +{ + _window = 0; + _graph_window = 0; + _tooltip_window = 0; + _sizewe_cursor = LoadCursor(nullptr, IDC_SIZEWE); + _hand_cursor = LoadCursor(nullptr, IDC_HAND); + _bitmap = 0; + _bitmap_dc = 0; + + _graph_left = 0; + _graph_top = 0; + _bitmap_xsize = 0; + _bitmap_ysize = 0; + + _pixel_scale = monitor->get_pixel_scale(); + + // Default margins. + int margin = _pixel_scale * 2; + _left_margin = margin; + _right_margin = margin; + _top_margin = margin; + _bottom_margin = margin; + _top_label_stack_margin = margin; + + _dark_color = RGB(51, 51, 51); + _light_color = RGB(154, 154, 154); + _user_guide_bar_color = RGB(130, 150, 255); + _frame_guide_bar_color = RGB(255, 10, 10); + _dark_pen = CreatePen(PS_SOLID, 1, _dark_color); + _light_pen = CreatePen(PS_SOLID, 1, _light_color); + _user_guide_bar_pen = CreatePen(PS_DASH, 1, _user_guide_bar_color); + _frame_guide_bar_pen = CreatePen(PS_DASH, 1, _frame_guide_bar_color); + + _drag_mode = DM_none; + _potential_drag_mode = DM_none; + _drag_scale_start = 0.0f; + + _pause = false; +} + +/** + * + */ +WinStatsGraph:: +~WinStatsGraph() { + _monitor = nullptr; + release_bitmap(); + + DeleteObject(_dark_pen); + DeleteObject(_light_pen); + DeleteObject(_user_guide_bar_pen); + DeleteObject(_frame_guide_bar_pen); + + for (auto &item : _brushes) { + DeleteObject(item.second.first); + DeleteObject(item.second.second); + } + _brushes.clear(); + _text_colors.clear(); + + if (_graph_window) { + DestroyWindow(_graph_window); + _graph_window = 0; + } + + if (_window) { + DestroyWindow(_window); + _window = 0; + } + + if (_tooltip_window) { + DestroyWindow(_tooltip_window); + _tooltip_window = 0; + } +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void WinStatsGraph:: +new_collector(int new_collector) { +} + +/** + * Called whenever new data arrives. + */ +void WinStatsGraph:: +new_data(int thread_index, int frame_number) { +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void WinStatsGraph:: +changed_graph_size(int graph_xsize, int graph_ysize) { +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void WinStatsGraph:: +set_time_units(int unit_mask) { +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void WinStatsGraph:: +set_scroll_speed(double scroll_speed) { +} + +/** + * Changes the pause flag for the graph. When this flag is true, the graph + * does not update in response to new data. + */ +void WinStatsGraph:: +set_pause(bool pause) { + _pause = pause; +} + +/** + * Called when the user guide bars have been changed. + */ +void WinStatsGraph:: +user_guide_bars_changed() { + InvalidateRect(_window, nullptr, TRUE); + InvalidateRect(_graph_window, nullptr, TRUE); +} + +/** + * Called when the user single-clicks on a label. + */ +void WinStatsGraph:: +on_click_label(int collector_index) { +} + +/** + * Called when a pop-up menu should be shown for the label. + */ +void WinStatsGraph:: +on_popup_label(int collector_index) { +} + +/** + * Called when the user hovers the mouse over a label. + */ +void WinStatsGraph:: +on_enter_label(int collector_index) { + if (collector_index != _highlighted_index) { + _highlighted_index = collector_index; + clear_graph_tooltip(); + force_redraw(); + } +} + +/** + * Called when the user's mouse cursor leaves a label. + */ +void WinStatsGraph:: +on_leave_label(int collector_index) { + if (collector_index == _highlighted_index && collector_index != -1) { + _highlighted_index = -1; + clear_graph_tooltip(); + force_redraw(); + } +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsGraph:: +get_label_tooltip(int collector_index) const { + return std::string(); +} + +/** + * Hides the graph tooltip. + */ +void WinStatsGraph:: +clear_graph_tooltip() { + if (_tooltip_window != 0) { + SendMessage(_tooltip_window, TTM_POP, 0, 0); + } +} + +/** + * Returns the window handle of the surrounding window. + */ +HWND WinStatsGraph:: +get_window() { + return _window; +} + +/** + * Should be called when the user closes the associated window. This tells + * the monitor to remove the graph. + */ +void WinStatsGraph:: +close() { + WinStatsMonitor *monitor = _monitor; + _monitor = nullptr; + if (monitor != nullptr) { + monitor->remove_graph(this); + } +} + +/** + * Sets up the label stack on the left edge of the frame. + */ +void WinStatsGraph:: +setup_label_stack() { + _label_stack.setup(_window); + move_label_stack(); +} + +/** + * Repositions the label stack if its coordinates or size have changed. + */ +void WinStatsGraph:: +move_label_stack() { + if (_label_stack.is_setup()) { + RECT rect; + GetClientRect(_window, &rect); + + rect.left += _pixel_scale * 2; + rect.right = _left_margin - _pixel_scale * 2; + + _label_stack.set_pos(rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + _top_label_stack_margin, _bottom_margin); + } +} + +/** + * Turns on the animation timer, if it hasn't already been turned on. + */ +void WinStatsGraph:: +start_animation() { + if (!_timer_running) { + TrueClock *clock = TrueClock::get_global_ptr(); + _time = clock->get_short_time(); + SetTimer(_window, 0x100, 16, nullptr); + _timer_running = true; + } +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool WinStatsGraph:: +animate(double time, double dt) { + return false; +} + +/** + * Returns the current window dimensions. + */ +void WinStatsGraph:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsServer *server = (WinStatsServer *)_monitor->get_server(); + POINT client_origin = server->get_client_origin(); + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(_window, &wp); + x = wp.rcNormalPosition.left - client_origin.x; + y = wp.rcNormalPosition.top - client_origin.y; + width = wp.rcNormalPosition.right - wp.rcNormalPosition.left; + height = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; + maximized = (wp.showCmd == SW_SHOWMAXIMIZED || (wp.flags & WPF_RESTORETOMAXIMIZED) != 0); + minimized = (wp.showCmd == SW_SHOWMINIMIZED); +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsGraph:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsServer *server = (WinStatsServer *)_monitor->get_server(); + POINT client_origin = server->get_client_origin(); + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + wp.flags = maximized ? WPF_RESTORETOMAXIMIZED : 0; + wp.showCmd = minimized ? SW_SHOWMINIMIZED : (maximized ? SW_SHOWMAXIMIZED : SW_SHOWNORMAL); + wp.ptMinPosition.x = -1; + wp.ptMinPosition.y = -1; + wp.ptMaxPosition.x = -1; + wp.ptMaxPosition.y = -1; + wp.rcNormalPosition.left = client_origin.x + x; + wp.rcNormalPosition.top = client_origin.y + y; + wp.rcNormalPosition.right = wp.rcNormalPosition.left + width; + wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + height; + + if (minimized) { + int x, y; + _monitor->calc_iconic_graph_window_pos(this, x, y); + wp.ptMinPosition.x = x; + wp.ptMinPosition.y = y; + wp.flags |= WPF_SETMINPOSITION; + } + + SetWindowPlacement(_window, &wp); +} + +/** + * Returns a brush suitable for drawing in the indicated collector's color. + */ +HBRUSH WinStatsGraph:: +get_collector_brush(int collector_index, bool highlight) { + Brushes::iterator bi; + bi = _brushes.find(collector_index); + if (bi != _brushes.end()) { + return highlight ? (*bi).second.second : (*bi).second.first; + } + + // Ask the monitor what color this guy should be. + LRGBColor rgb = _monitor->get_collector_color(collector_index); + int r = (int)encode_sRGB_uchar((float)rgb[0]); + int g = (int)encode_sRGB_uchar((float)rgb[1]); + int b = (int)encode_sRGB_uchar((float)rgb[2]); + HBRUSH brush = CreateSolidBrush(RGB(r, g, b)); + + int hr = (int)encode_sRGB_uchar((float)rgb[0] * 0.75f); + int hg = (int)encode_sRGB_uchar((float)rgb[1] * 0.75f); + int hb = (int)encode_sRGB_uchar((float)rgb[2] * 0.75f); + HBRUSH hbrush = CreateSolidBrush(RGB(hr, hg, hb)); + + _brushes[collector_index] = std::make_pair(brush, hbrush); + return highlight ? hbrush : brush; +} + +/** + * Returns a text color suitable for the given collector. + */ +COLORREF WinStatsGraph:: +get_collector_text_color(int collector_index, bool highlight) { + TextColors::iterator tci; + tci = _text_colors.find(collector_index); + if (tci != _text_colors.end()) { + return highlight ? (*tci).second.second : (*tci).second.first; + } + + LRGBColor rgb = _monitor->get_collector_color(collector_index); + double bright = + rgb[0] * 0.2126 + + rgb[1] * 0.7152 + + rgb[2] * 0.0722; + COLORREF color = bright >= 0.5 ? RGB(0, 0, 0) : RGB(255, 255, 255); + COLORREF hcolor = bright * 0.75 >= 0.5 ? RGB(0, 0, 0) : RGB(255, 255, 255); + + _text_colors[collector_index] = std::make_pair(color, hcolor); + return highlight ? hcolor : color; +} + +/** + * Called when the given collector has changed colors. + */ +void WinStatsGraph:: +reset_collector_color(int collector_index) { + _brushes.erase(collector_index); + _text_colors.erase(collector_index); + force_redraw(); + _label_stack.update_label_color(collector_index); +} + +/** + * This window_proc should be called up to by the derived classes for any + * messages that are not specifically handled by the derived class. + */ +LONG WinStatsGraph:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_DESTROY: + close(); + break; + + case WM_APPCOMMAND: + if (GET_APPCOMMAND_LPARAM(lparam) == APPCOMMAND_CLOSE) { + close(); + return TRUE; + } + break; + + case WM_GETMINMAXINFO: + { + WINDOWINFO winfo; + GetWindowInfo(hwnd, &winfo); + MINMAXINFO &minmax = *(MINMAXINFO *)lparam; + int vminheight = _bottom_margin + _top_margin; + if (_label_stack.get_num_labels() > 0) { + // If we have a label, make sure at least one can be shown. + vminheight = (std::max)(vminheight, _top_label_stack_margin + _label_stack.get_label_height(0) + _bottom_margin); + } + minmax.ptMinTrackSize.x = (winfo.rcClient.left - winfo.rcWindow.left) + + (winfo.rcWindow.right - winfo.rcClient.right) + + (_right_margin + _left_margin); + minmax.ptMinTrackSize.y = (winfo.rcClient.top - winfo.rcWindow.top) + + (winfo.rcWindow.bottom - winfo.rcClient.bottom) + + vminheight; + return 0; + } + + case WM_WINDOWPOSCHANGING: + { + WINDOWPOS &pos = *(WINDOWPOS *)lparam; + if ((pos.flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0 && IsIconic(hwnd)) { + _monitor->calc_iconic_graph_window_pos(this, pos.x, pos.y); + } + } + break; + + case WM_SYSCOMMAND: + if (wparam == SC_MINIMIZE) { + WINDOWPLACEMENT wp; + if (GetWindowPlacement(hwnd, &wp)) { + int x, y; + _monitor->calc_iconic_graph_window_pos(this, x, y); + wp.showCmd = SW_SHOWMINIMIZED; + wp.flags |= WPF_SETMINPOSITION; + wp.ptMinPosition.x = x; + wp.ptMinPosition.y = y; + SetWindowPlacement(hwnd, &wp); + } + return 0; + } + break; + + case WM_SIZE: + move_label_stack(); + InvalidateRect(hwnd, nullptr, TRUE); + break; + + case WM_SIZING: + set_drag_mode(DM_sizing); + break; + + case WM_EXITSIZEMOVE: + set_drag_mode(DM_none); + break; + + case WM_SETCURSOR: + { + // Why is it so hard to ask for the cursor position within the window's + // client area? + POINT point; + GetCursorPos(&point); + WINDOWINFO winfo; + GetWindowInfo(hwnd, &winfo); + const RECT &rect = winfo.rcClient; + int x = point.x - rect.left; + int y = point.y - rect.top; + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + + _potential_drag_mode = consider_drag_start(x, y, width, height); + + switch (_potential_drag_mode) { + case DM_left_margin: + case DM_right_margin: + SetCursor(_sizewe_cursor); + return TRUE; + + case DM_guide_bar: + SetCursor(_hand_cursor); + return TRUE; + + default: + case DM_none: + break; + } + } + break; + + case WM_LBUTTONDOWN: + if (_potential_drag_mode != DM_none) { + set_drag_mode(_potential_drag_mode); + _drag_start_x = (int16_t)LOWORD(lparam); + _drag_start_y = (int16_t)HIWORD(lparam); + SetCapture(_window); + } + return 0; + + case WM_MOUSEMOVE: + if (_drag_mode == DM_left_margin) { + int16_t x = LOWORD(lparam); + int new_left_margin = _left_margin + (x - _drag_start_x); + _left_margin = std::max(new_left_margin, _pixel_scale * 2); + _drag_start_x = x - (new_left_margin - _left_margin); + InvalidateRect(hwnd, nullptr, TRUE); + move_label_stack(); + return 0; + + } else if (_drag_mode == DM_right_margin) { + int16_t x = LOWORD(lparam); + _right_margin += (_drag_start_x - x); + _drag_start_x = x; + InvalidateRect(hwnd, nullptr, TRUE); + return 0; + } + break; + + case WM_LBUTTONUP: + set_drag_mode(DM_none); + ReleaseCapture(); + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + // First, draw a frame around the graph. + RECT rect; + GetClientRect(hwnd, &rect); + + rect.left += _left_margin; + rect.top += _top_margin; + rect.right -= _right_margin; + rect.bottom -= _bottom_margin; + + if (rect.right > rect.left && rect.bottom > rect.top) { + int graph_xsize = rect.right - rect.left; + int graph_ysize = rect.bottom - rect.top; + if (_bitmap_dc == 0 || + graph_xsize != _bitmap_xsize || + graph_ysize != _bitmap_ysize) { + // Oops, we need to change the bitmap (and graph) size. + changed_graph_size(graph_xsize, graph_ysize); + move_graph_window(rect.left, rect.top, graph_xsize, graph_ysize); + force_redraw(); + } + } + + additional_window_paint(hdc); + + EndPaint(hwnd, &ps); + return 0; + } + + case WM_DRAWITEM: + if (wparam == IDC_GRAPH) { + const DRAWITEMSTRUCT &dis = *(DRAWITEMSTRUCT *)lparam; + + // Repaint the graph by copying the backing pixmap in. + BitBlt(dis.hDC, 0, 0, + _bitmap_xsize, _bitmap_ysize, + _bitmap_dc, 0, 0, + SRCCOPY); + + additional_graph_window_paint(dis.hDC); + return 0; + } + break; + + case WM_TIMER: + { + TrueClock *clock = TrueClock::get_global_ptr(); + double new_time = clock->get_short_time(); + if (!animate(new_time, new_time - _time)) { + KillTimer(hwnd, 0x100); + _timer_running = false; + } + _time = new_time; + } + return 0; + + case WM_NOTIFY: + switch (((LPNMHDR)lparam)->code) { + case TTN_GETDISPINFO: + { + NMTTDISPINFO &info = *(NMTTDISPINFO *)lparam; + POINT point; + if (GetCursorPos(&point) && ScreenToClient(_graph_window, &point)) { + _tooltip_text = get_graph_tooltip(point.x, point.y); + info.lpszText = (char *)_tooltip_text.c_str(); + } + } + return 0; + } + break; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +LONG WinStatsGraph:: +graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_DISPLAYCHANGE: + setup_bitmap(_bitmap_xsize, _bitmap_ysize); + force_redraw(); + break; + + case WM_NCHITTEST: + // Necessary for mouse events to work; default returns HTTRANSPARENT + return HTCLIENT; + + case WM_LBUTTONDOWN: + // Vector any uncaught WM_LBUTTONDOWN into the main window, so we can drag + // margins, etc. + if (_potential_drag_mode != DM_none) { + int16_t x = LOWORD(lparam) + _graph_left; + int16_t y = HIWORD(lparam) + _graph_top; + return window_proc(_window, msg, wparam, MAKELPARAM(x, y)); + } + break; + + case WM_LBUTTONUP: + set_drag_mode(DM_none); + ReleaseCapture(); + break; + + default: + break; + } + + return DefSubclassProc(hwnd, msg, wparam, lparam); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsGraph:: +additional_window_paint(HDC hdc) { +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the graph window. + */ +void WinStatsGraph:: +additional_graph_window_paint(HDC hdc) { +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsGraph:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return std::string(); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * appropriate DragMode enum or DM_none if nothing is indicated. + */ +WinStatsGraph::DragMode WinStatsGraph:: +consider_drag_start(int mouse_x, int mouse_y, int width, int height) { + if (mouse_x >= _left_margin - 2 && mouse_x <= _left_margin + 2) { + return DM_left_margin; + } else if (mouse_x >= width - _right_margin - 2 && mouse_x <= width - _right_margin + 2) { + return DM_right_margin; + } + + return DM_none; +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void WinStatsGraph:: +set_drag_mode(WinStatsGraph::DragMode drag_mode) { + _drag_mode = drag_mode; +} + +/** + * Repositions the graph child window within the parent window according to + * the _margin variables. + */ +void WinStatsGraph:: +move_graph_window(int graph_left, int graph_top, int graph_xsize, int graph_ysize) { + if (_graph_window == 0) { + create_graph_window(); + } + + _graph_left = graph_left; + _graph_top = graph_top; + + SetWindowPos(_graph_window, 0, + _graph_left, _graph_top, + graph_xsize, graph_ysize, + SWP_NOZORDER | SWP_SHOWWINDOW); + + if (graph_xsize != _bitmap_xsize || graph_ysize != _bitmap_ysize) { + setup_bitmap(graph_xsize, graph_ysize); + } +} + +/** + * Sets up a backing-store bitmap of the indicated size. + */ +void WinStatsGraph:: +setup_bitmap(int xsize, int ysize) { + release_bitmap(); + _bitmap_xsize = std::max(xsize, 0); + _bitmap_ysize = std::max(ysize, 0); + + HDC hdc = GetDC(_graph_window); + _bitmap_dc = CreateCompatibleDC(hdc); + _bitmap = CreateCompatibleBitmap(hdc, _bitmap_xsize, _bitmap_ysize); + SelectObject(_bitmap_dc, _bitmap); + + RECT rect = { 0, 0, _bitmap_xsize, _bitmap_ysize }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); + + ReleaseDC(_window, hdc); +} + +/** + * Frees the backing-store bitmap created by setup_bitmap(). + */ +void WinStatsGraph:: +release_bitmap() { + if (_bitmap) { + DeleteObject(_bitmap); + _bitmap = 0; + } + if (_bitmap_dc) { + DeleteDC(_bitmap_dc); + _bitmap_dc = 0; + } +} + +/** + * Creates the child window that actually holds the graph. + */ +void WinStatsGraph:: +create_graph_window() { + if (_graph_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + + DWORD window_style = WS_CHILD | WS_CLIPSIBLINGS | + SS_SUNKEN | SS_OWNERDRAW; + + _graph_window = + CreateWindow(WC_STATIC, "", window_style, 0, 0, 0, 0, + _window, (HMENU)IDC_GRAPH, application, 0); + if (!_graph_window) { + nout << "Could not create graph window!\n"; + exit(1); + } + + EnableWindow(_graph_window, TRUE); + + SetWindowSubclass(_graph_window, &static_graph_subclass_proc, 1234, (DWORD_PTR)this); + + // Create the tooltip window. This will cause a TTN_GETDISPINFO message to + // be sent to the window to acquire the tooltip text. + _tooltip_window = CreateWindow(TOOLTIPS_CLASS, nullptr, + WS_POPUP, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + _window, nullptr, + application, nullptr); + + if (_tooltip_window != 0) { + TOOLINFO info = { 0 }; + info.cbSize = sizeof(info); + info.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + info.hwnd = _window; + info.uId = (UINT_PTR)_graph_window; + info.lpszText = LPSTR_TEXTCALLBACK; + SendMessage(_tooltip_window, TTM_ADDTOOL, 0, (LPARAM)&info); + } +} + +/** + * + */ +LRESULT WINAPI WinStatsGraph:: +static_graph_subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR subclass, DWORD_PTR ref_data) { + WinStatsGraph *self = (WinStatsGraph *)ref_data; + if (self != nullptr && self->_graph_window == hwnd) { + return self->graph_window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} diff --git a/pandatool/src/win-stats/winStatsGraph.h b/pandatool/src/win-stats/winStatsGraph.h new file mode 100644 index 00000000..e1a9f219 --- /dev/null +++ b/pandatool/src/win-stats/winStatsGraph.h @@ -0,0 +1,164 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsGraph.h + * @author drose + * @date 2003-12-03 + */ + +#ifndef WINSTATSGRAPH_H +#define WINSTATSGRAPH_H + +#include "pandatoolbase.h" +#include "winStatsLabelStack.h" +#include "pmap.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class PStatGraph; +class WinStatsMonitor; + +/** + * This is just an abstract base class to provide a common pointer type for + * the various kinds of graphs that may be created for a WinStatsMonitor. + */ +class WinStatsGraph { +public: + // What is the user adjusting by dragging the mouse in a window? + enum DragMode { + DM_none, + DM_scale, + DM_left_margin, + DM_right_margin, + DM_guide_bar, + DM_new_guide_bar, + DM_sizing, + DM_pan, + }; + +public: + WinStatsGraph(WinStatsMonitor *monitor); + virtual ~WinStatsGraph(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw()=0; + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void user_guide_bars_changed(); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual void on_enter_label(int collector_index); + virtual void on_leave_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + + void clear_graph_tooltip(); + + HWND get_window(); + + void reset_collector_color(int collector_index); + +protected: + void close(); + + void setup_label_stack(); + void move_label_stack(); + + void start_animation(); + virtual bool animate(double time, double dt); + + void get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + HBRUSH get_collector_brush(int collector_index, bool highlight = false); + COLORREF get_collector_text_color(int collector_index, bool highlight = false); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + virtual void additional_window_paint(HDC hdc); + virtual void additional_graph_window_paint(HDC hdc); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int mouse_x, int mouse_y, + int width, int height); + virtual void set_drag_mode(DragMode drag_mode); + + virtual void move_graph_window(int graph_left, int graph_top, + int graph_xsize, int graph_ysize); + +protected: + // Table of brushes for our various collectors. + typedef pmap > Brushes; + Brushes _brushes; + + typedef pmap > TextColors; + TextColors _text_colors; + + WinStatsMonitor *_monitor; + HWND _window; + HWND _graph_window; + HWND _tooltip_window; + WinStatsLabelStack _label_stack; + std::string _tooltip_text; + + HCURSOR _sizewe_cursor; + HCURSOR _hand_cursor; + + HBITMAP _bitmap; + HDC _bitmap_dc; + + int _graph_left, _graph_top; + int _bitmap_xsize, _bitmap_ysize; + int _left_margin, _right_margin; + int _top_margin, _bottom_margin; + int _top_label_stack_margin; + int _pixel_scale; + + COLORREF _dark_color; + COLORREF _light_color; + COLORREF _user_guide_bar_color; + COLORREF _frame_guide_bar_color; + HPEN _dark_pen; + HPEN _light_pen; + HPEN _user_guide_bar_pen; + HPEN _frame_guide_bar_pen; + + DragMode _drag_mode; + DragMode _potential_drag_mode; + int _drag_start_x, _drag_start_y; + double _drag_scale_start; + int _drag_guide_bar; + + int _highlighted_index = -1; + + bool _pause; + + bool _timer_running = false; + double _time; + +private: + void setup_bitmap(int xsize, int ysize); + void release_bitmap(); + void create_graph_window(); + + static LRESULT WINAPI static_graph_subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT_PTR subclass, DWORD_PTR ref_data); + +protected: + static DWORD graph_window_style; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsLabel.I b/pandatool/src/win-stats/winStatsLabel.I new file mode 100644 index 00000000..a7cabe90 --- /dev/null +++ b/pandatool/src/win-stats/winStatsLabel.I @@ -0,0 +1,68 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsLabel.I + * @author rdb + * @date 2022-01-29 + */ + +/** + * Returns the x position of the label on its parent. + */ +INLINE int WinStatsLabel:: +get_x() const { + return _x; +} + +/** + * Returns the y position of the label on its parent. + */ +INLINE int WinStatsLabel:: +get_y() const { + return _y; +} + +/** + * Returns the width of the label as we requested it. + */ +INLINE int WinStatsLabel:: +get_width() const { + return _width; +} + +/** + * Returns the height of the label as we requested it. + */ +INLINE int WinStatsLabel:: +get_height() const { + return _height; +} + +/** + * Returns the width the label would really prefer to be. + */ +INLINE int WinStatsLabel:: +get_ideal_width() const { + return _ideal_width; +} + +/** + * Returns the collector this label represents. + */ +INLINE int WinStatsLabel:: +get_collector_index() const { + return _collector_index; +} + +/** + * Returns true if the visual highlight for this label is enabled. + */ +INLINE bool WinStatsLabel:: +get_highlight() const { + return _highlight; +} diff --git a/pandatool/src/win-stats/winStatsLabel.cxx b/pandatool/src/win-stats/winStatsLabel.cxx new file mode 100644 index 00000000..007f7505 --- /dev/null +++ b/pandatool/src/win-stats/winStatsLabel.cxx @@ -0,0 +1,390 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsLabel.cxx + * @author drose + * @date 2004-01-07 + */ + +#include "winStatsLabel.h" +#include "winStatsMonitor.h" +#include "winStatsGraph.h" +#include "convert_srgb.h" + +#include + +int WinStatsLabel::_left_margin = 2; +int WinStatsLabel::_right_margin = 2; +int WinStatsLabel::_top_margin = 2; +int WinStatsLabel::_bottom_margin = 2; + +bool WinStatsLabel::_window_class_registered = false; +const char * const WinStatsLabel::_window_class_name = "label"; + +/** + * + */ +WinStatsLabel:: +WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname, + bool align_right) : + _monitor(monitor), + _graph(graph), + _thread_index(thread_index), + _collector_index(collector_index), + _align_right(align_right), + _window(0), + _tooltip_window(0) +{ + update_text(use_fullname); + update_color(); + + _x = 0; + _y = 0; + _width = 0; + _height = 0; + _ideal_width = 0; + _highlight = false; + _mouse_within = false; +} + +/** + * + */ +WinStatsLabel:: +~WinStatsLabel() { + if (_window) { + if (_tooltip_window) { + DestroyWindow(_tooltip_window); + _tooltip_window = 0; + } + DestroyWindow(_window); + _window = 0; + } + DeleteObject(_bg_brush); + DeleteObject(_highlight_bg_brush); +} + +/** + * Creates the actual window. + */ +void WinStatsLabel:: +setup(HWND parent_window) { + if (_window) { + DestroyWindow(_window); + _window = 0; + } + + create_window(parent_window); + + HDC hdc = GetDC(_window); + HFONT hfnt = _monitor->get_font(); + SelectObject(hdc, hfnt); + + SIZE size; + GetTextExtentPoint32(hdc, _text.data(), _text.length(), &size); + _height = size.cy + _top_margin + _bottom_margin; + _ideal_width = size.cx + _left_margin + _right_margin; + + ReleaseDC(_window, hdc); +} + +/** + * Sets the position of the label on its parent. The position describes the + * lower-left corner of the rectangle, not the upper-left. + */ +void WinStatsLabel:: +set_pos(int x, int y, int width) { + if (x != _x || y != _y || width != _width) { + _x = x; + _y = y; + _width = width; + SetWindowPos(_window, 0, x, y - _height, _width, _height, + SWP_NOZORDER | SWP_SHOWWINDOW); + } +} + +/** + * Changes the Y attribute without updating the window. + */ +void WinStatsLabel:: +set_y_noupdate(int y) { + _y = y; +} + +/** + * Enables or disables the visual highlight for this label. + */ +void WinStatsLabel:: +set_highlight(bool highlight) { + if (_highlight != highlight) { + _highlight = highlight; + InvalidateRect(_window, nullptr, TRUE); + } +} + +/** + * Updates the colors. + */ +void WinStatsLabel:: +update_color() { + if (_bg_brush != 0) { + DeleteObject(_bg_brush); + } + if (_highlight_bg_brush != 0) { + DeleteObject(_highlight_bg_brush); + } + + LRGBColor rgb = _monitor->get_collector_color(_collector_index); + int r = (int)encode_sRGB_uchar((float)rgb[0]); + int g = (int)encode_sRGB_uchar((float)rgb[1]); + int b = (int)encode_sRGB_uchar((float)rgb[2]); + _bg_brush = CreateSolidBrush(RGB(r, g, b)); + + // Calculate the color when it is highlighted. + int hr = (int)encode_sRGB_uchar((float)rgb[0] * 0.75f); + int hg = (int)encode_sRGB_uchar((float)rgb[1] * 0.75f); + int hb = (int)encode_sRGB_uchar((float)rgb[2] * 0.75f); + _highlight_bg_brush = CreateSolidBrush(RGB(hr, hg, hb)); + + // Should our foreground be black or white? + double bright = + rgb[0] * 0.2126 + + rgb[1] * 0.7152 + + rgb[2] * 0.0722; + + if (bright >= 0.5) { + _fg_color = RGB(0, 0, 0); + } else { + _fg_color = RGB(255, 255, 255); + } + if (bright * 0.75 >= 0.5) { + _highlight_fg_color = RGB(0, 0, 0); + } else { + _highlight_fg_color = RGB(255, 255, 255); + } + + if (_window) { + InvalidateRect(_window, nullptr, TRUE); + } +} + +/** + * Set to true if the full name of the collector should be shown. + */ +void WinStatsLabel:: +update_text(bool use_fullname) { + const PStatClientData *client_data = _monitor->get_client_data(); + _tooltip_text = client_data->get_collector_fullname(_collector_index); + if (use_fullname) { + _text = _tooltip_text; + } else { + _text = client_data->get_collector_name(_collector_index); + } + + // Recalculate the dimensions. + if (_window) { + HDC hdc = GetDC(_window); + HFONT hfnt = _monitor->get_font(); + SelectObject(hdc, hfnt); + + SIZE size; + GetTextExtentPoint32(hdc, _text.data(), _text.length(), &size); + _height = size.cy + _top_margin + _bottom_margin; + _ideal_width = size.cx + _left_margin + _right_margin; + } +} + +/** + * Used internally to indicate whether the mouse is within the label's window. + */ +void WinStatsLabel:: +set_mouse_within(bool mouse_within) { + if (_mouse_within != mouse_within) { + _mouse_within = mouse_within; + InvalidateRect(_window, nullptr, TRUE); + } +} + +/** + * Creates the window for this label. + */ +void WinStatsLabel:: +create_window(HWND parent_window) { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + _window = + CreateWindow(_window_class_name, _text.c_str(), WS_CHILD | WS_CLIPSIBLINGS, + 0, 0, 0, 0, + parent_window, nullptr, application, 0); + if (!_window) { + nout << "Could not create Label window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + + // Create the tooltip window. This will cause a TTN_GETDISPINFO message to + // be sent to the window to acquire the tooltip text. + _tooltip_window = CreateWindow(TOOLTIPS_CLASS, nullptr, + WS_POPUP, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + _window, nullptr, + application, nullptr); + + if (_tooltip_window != 0) { + TOOLINFO info = { 0 }; + info.cbSize = sizeof(info); + info.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + info.hwnd = _window; + info.uId = (UINT_PTR)_window; + info.lpszText = LPSTR_TEXTCALLBACK; + SendMessage(_tooltip_window, TTM_ADDTOOL, 0, (LPARAM)&info); + } +} + +/** + * Registers the window class for the label window, if it has not already been + * registered. + */ +void WinStatsLabel:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsLabel *); + + if (!RegisterClass(&wc)) { + nout << "Could not register Label window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsLabel:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsLabel *self = (WinStatsLabel *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +/** + * + */ +LONG WinStatsLabel:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDBLCLK: + _graph->on_click_label(_collector_index); + return 0; + + case WM_CONTEXTMENU: + _graph->on_popup_label(_collector_index); + return 0; + + case WM_MOUSEMOVE: + { + // When the mouse enters the label area, highlight the label. + if (!_mouse_within) { + set_mouse_within(true); + _graph->on_enter_label(_collector_index); + } + + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the label. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _window, + 0 + }; + TrackMouseEvent(&tme); + } + break; + + case WM_MOUSELEAVE: + if (_mouse_within) { + set_mouse_within(false); + _graph->on_leave_label(_collector_index); + } + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + RECT rect = { 0, 0, _width, _height }; + FillRect(hdc, &rect, (_highlight || _mouse_within) ? _highlight_bg_brush : _bg_brush); + + HFONT hfnt = _monitor->get_font(); + SelectObject(hdc, hfnt); + SetTextAlign(hdc, TA_LEFT | TA_TOP | TA_NOUPDATECP); + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, (_highlight || _mouse_within) ? _highlight_fg_color : _fg_color); + + if (_width > 8) { + UINT format = DT_END_ELLIPSIS | DT_SINGLELINE; + if (_align_right) { + format |= DT_RIGHT; + } else { + format |= DT_LEFT; + } + + RECT margins = { _left_margin, _top_margin, _width - _right_margin, _height - _bottom_margin }; + DrawText(hdc, _text.data(), _text.length(), &margins, format); + } + + EndPaint(hwnd, &ps); + return 0; + } + + case WM_NOTIFY: + switch (((LPNMHDR)lparam)->code) { + case TTN_GETDISPINFO: + { + NMTTDISPINFO &info = *(NMTTDISPINFO *)lparam; + _tooltip_text = _graph->get_label_tooltip(_collector_index); + info.lpszText = (char *)_tooltip_text.c_str(); + } + return 0; + } + break; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/pandatool/src/win-stats/winStatsLabel.h b/pandatool/src/win-stats/winStatsLabel.h new file mode 100644 index 00000000..3ad3b393 --- /dev/null +++ b/pandatool/src/win-stats/winStatsLabel.h @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsLabel.h + * @author drose + * @date 2004-01-07 + */ + +#ifndef WINSTATSLABEL_H +#define WINSTATSLABEL_H + +#include "pandatoolbase.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsMonitor; +class WinStatsGraph; + +/** + * A text label that will draw in color appropriate for a particular + * collector. It also responds when the user double-clicks on it. This is + * handy for putting colored labels on strip charts. + */ +class WinStatsLabel { +public: + WinStatsLabel(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname, + bool align_right = true); + ~WinStatsLabel(); + + void setup(HWND parent_window); + void set_pos(int x, int y, int width); + void set_y_noupdate(int y); + + INLINE int get_x() const; + INLINE int get_y() const; + INLINE int get_width() const; + INLINE int get_height() const; + INLINE int get_ideal_width() const; + + INLINE int get_collector_index() const; + + void set_highlight(bool highlight); + INLINE bool get_highlight() const; + + void update_color(); + void update_text(bool use_fullname); + +private: + void set_mouse_within(bool mouse_within); + + void create_window(HWND parent_window); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + WinStatsMonitor *_monitor; + WinStatsGraph *_graph; + int _thread_index; + int _collector_index; + std::string _text; + std::string _tooltip_text; + HWND _window; + HWND _tooltip_window; + COLORREF _fg_color; + COLORREF _highlight_fg_color; + HBRUSH _bg_brush = 0; + HBRUSH _highlight_bg_brush = 0; + + int _x; + int _y; + int _width; + int _height; + int _ideal_width; + bool _highlight; + bool _mouse_within; + bool _align_right; + + static int _left_margin, _right_margin; + static int _top_margin, _bottom_margin; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#include "winStatsLabel.I" + +#endif diff --git a/pandatool/src/win-stats/winStatsLabelStack.cxx b/pandatool/src/win-stats/winStatsLabelStack.cxx new file mode 100644 index 00000000..be3a5971 --- /dev/null +++ b/pandatool/src/win-stats/winStatsLabelStack.cxx @@ -0,0 +1,465 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsLabelStack.cxx + * @author drose + * @date 2004-01-07 + */ + +#include "winStatsLabelStack.h" +#include "winStatsLabel.h" +#include "pnotify.h" + +bool WinStatsLabelStack::_window_class_registered = false; +const char * const WinStatsLabelStack::_window_class_name = "stack"; + +/** + * + */ +WinStatsLabelStack:: +WinStatsLabelStack() { + _x = 0; + _y = 0; + _width = 0; + _height = 0; + _ideal_width = 0; + + _highlight_label = -1; +} + +/** + * + */ +WinStatsLabelStack:: +~WinStatsLabelStack() { + clear_labels(); + if (_window) { + DestroyWindow(_window); + _window = 0; + } +} + +/** + * Creates the actual window object. + */ +void WinStatsLabelStack:: +setup(HWND parent_window) { + if (_window) { + DestroyWindow(_window); + _window = 0; + } + + create_window(parent_window); + + _ideal_width = 0; + for (WinStatsLabel *label : _labels) { + label->setup(_window); + _ideal_width = std::max(_ideal_width, label->get_ideal_width()); + } +} + +/** + * Returns true if the label stack has been set up, false otherwise. + */ +bool WinStatsLabelStack:: +is_setup() const { + return (_window != 0); +} + +/** + * Sets the position and size of the label stack on its parent. + */ +void WinStatsLabelStack:: +set_pos(int x, int y, int width, int height, int top_margin, int bottom_margin) { + _x = x; + _y = y; + _width = width; + _height = height; + _top_margin = top_margin; + _bottom_margin = bottom_margin; + SetWindowPos(_window, 0, x, y, _width, _height, + SWP_NOZORDER | SWP_SHOWWINDOW); + + recalculate_label_positions(); +} + +/** + * Returns the x position of the stack on its parent. + */ +int WinStatsLabelStack:: +get_x() const { + return _x; +} + +/** + * Returns the y position of the stack on its parent. + */ +int WinStatsLabelStack:: +get_y() const { + return _y; +} + +/** + * Returns the width of the stack as we requested it. + */ +int WinStatsLabelStack:: +get_width() const { + return _width; +} + +/** + * Returns the height of the stack as we requested it. + */ +int WinStatsLabelStack:: +get_height() const { + return _height; +} + +/** + * Returns the width the stack would really prefer to be. + */ +int WinStatsLabelStack:: +get_ideal_width() const { + return _ideal_width; +} + +/** + * Returns the y position of the indicated label's bottom edge, relative to + * the label stack's parent window. + */ +int WinStatsLabelStack:: +get_label_y(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + return _labels[label_index]->get_y() + get_y(); +} + +/** + * Returns the height of the indicated label. + */ +int WinStatsLabelStack:: +get_label_height(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0); + return _labels[label_index]->get_height(); +} + +/** + * Returns the collector index associated with the indicated label. + */ +int WinStatsLabelStack:: +get_label_collector_index(int label_index) const { + nassertr(label_index >= 0 && label_index < (int)_labels.size(), -1); + return _labels[label_index]->get_collector_index(); +} + +/** + * Removes the set of labels and starts a new set. + */ +void WinStatsLabelStack:: +clear_labels() { + for (WinStatsLabel *label : _labels) { + delete label; + } + _labels.clear(); + _ideal_width = 0; + _scroll = 0; +} + +/** + * Adds a new label to the top of the stack; returns the new label index. + */ +int WinStatsLabelStack:: +add_label(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname) { + int yp = _height; + if (!_labels.empty()) { + WinStatsLabel *top_label = _labels.back(); + yp = top_label->get_y() - top_label->get_height(); + } + WinStatsLabel *label = + new WinStatsLabel(monitor, graph, thread_index, collector_index, use_fullname); + if (_window) { + label->setup(_window); + label->set_pos(0, yp - _scroll, _width); + } + _ideal_width = std::max(_ideal_width, label->get_ideal_width()); + + int label_index = (int)_labels.size(); + _labels.push_back(label); + + recalculate_label_positions(); + + return label_index; +} + +/** + * Replaces the labels with the given collector indices. + */ +void WinStatsLabelStack:: +replace_labels(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, const vector_int &collector_indices, + bool use_fullname) { + + _ideal_width = 0; + + // First skip the part of the stack that hasn't changed. + size_t li = 0; + size_t ci = 0; + while (ci < collector_indices.size() && li < _labels.size()) { + WinStatsLabel *label = _labels[li]; + if (collector_indices[ci] != label->get_collector_index()) { + // Mismatch. + break; + } + _ideal_width = std::max(_ideal_width, label->get_ideal_width()); + ++ci; + ++li; + } + + if (ci == collector_indices.size()) { + if (ci == _labels.size()) { + // Perfect, nothing changed. + return; + } + + // Simple case, just delete the rest. + while (li < _labels.size()) { + delete _labels[li++]; + } + _labels.resize(ci); + return; + } + + int yp = _height; + if (li > 0) { + WinStatsLabel *label = _labels[li - 1]; + yp = label->get_y() - label->get_height(); + } + + // Make a map of remaining labels. + std::map label_map; + for (size_t li2 = li; li2 < _labels.size(); ++li2) { + WinStatsLabel *label = _labels[li2]; + label_map[label->get_collector_index()] = label; + } + + _labels.resize(collector_indices.size()); + + while (ci < collector_indices.size()) { + int collector_index = collector_indices[ci++]; + + WinStatsLabel *label; + auto it = label_map.find(collector_index); + if (it == label_map.end()) { + // It's not in the map. Create a new label. + label = new WinStatsLabel(monitor, graph, thread_index, collector_index, use_fullname); + if (_window) { + label->setup(_window); + } + } else { + // Erase it from the map, so that it's not deleted. + label = it->second; + label_map.erase(it); + } + if (_window) { + label->set_pos(0, yp - _scroll, _width); + } + _ideal_width = std::max(_ideal_width, label->get_ideal_width()); + yp -= label->get_height(); + + _labels[li++] = label; + } + + // Anything that's remaining in the label map should be deleted. + for (auto it = label_map.begin(); it != label_map.end(); ++it) { + delete it->second; + } + + recalculate_label_positions(); +} + +/** + * Returns the number of labels in the stack. + */ +int WinStatsLabelStack:: +get_num_labels() const { + return _labels.size(); +} + +/** + * Draws a highlight around the label representing the indicated collector, + * and removes the highlight from any other label. Specify -1 to remove the + * highlight from all labels. + */ +void WinStatsLabelStack:: +highlight_label(int collector_index) { + if (_highlight_label != collector_index) { + _highlight_label = collector_index; + + for (WinStatsLabel *label : _labels) { + label->set_highlight(label->get_collector_index() == _highlight_label); + } + } +} + +/** + * Refreshes the color of the label with the given index. + */ +void WinStatsLabelStack:: +update_label_color(int collector_index) { + for (WinStatsLabel *label : _labels) { + if (label->get_collector_index() == collector_index) { + label->update_color(); + } + } +} + +/** + * Called to recalculate the positions of all labels in the stack. + */ +void WinStatsLabelStack:: +recalculate_label_positions() { + int total_height = 0; + for (WinStatsLabel *label : _labels) { + total_height += label->get_height(); + } + total_height += _bottom_margin + _top_margin; + int yp; + if (total_height <= _height) { + // Fits. Align to bottom and reset scroll. + yp = _height - _bottom_margin; + _scroll = 0; + } else { + // Doesn't fit. Align to top. + yp = total_height - _bottom_margin; + _scroll = (std::min)(_scroll, total_height - _height); + _scroll = (std::max)(_scroll, 0); + } + for (WinStatsLabel *label : _labels) { + label->set_pos(0, yp - _scroll, _width); + yp -= label->get_height(); + } +} + +/** + * Creates the window for this stack. + */ +void WinStatsLabelStack:: +create_window(HWND parent_window) { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + _window = + CreateWindow(_window_class_name, "label stack", WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, 0, 0, 0, + parent_window, nullptr, application, 0); + if (!_window) { + nout << "Could not create Label Stack window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); +} + +/** + * Registers the window class for the label window, if it has not already been + * registered. + */ +void WinStatsLabelStack:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsLabelStack *); + + if (!RegisterClass(&wc)) { + nout << "Could not register Label Stack window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsLabelStack:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsLabelStack *self = (WinStatsLabelStack *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +/** + * + */ +LONG WinStatsLabelStack:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + RECT rect = { 0, 0, _width, _height }; + FillRect(hdc, &rect, (HBRUSH)COLOR_WINDOW); + EndPaint(hwnd, &ps); + return 0; + } + + case WM_MOUSEWHEEL: + { + int total_height = 0; + for (WinStatsLabel *label : _labels) { + total_height += label->get_height(); + } + total_height += _bottom_margin + _top_margin; + if ((total_height > _height || _scroll != 0) && !_labels.empty()) { + int delta = GET_WHEEL_DELTA_WPARAM(wparam); + delta = (delta * _labels[0]->get_height()) / 120; + int new_scroll = _scroll - delta; + new_scroll = (std::min)(new_scroll, total_height - _height); + new_scroll = (std::max)(new_scroll, 0); + delta = new_scroll - _scroll; + if (delta != 0) { + _scroll = new_scroll; + ScrollWindowEx(_window, 0, -delta, NULL, NULL, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN); + int yp = yp = total_height - _bottom_margin; + for (WinStatsLabel *label : _labels) { + label->set_y_noupdate(yp - _scroll); + yp -= label->get_height(); + } + } + } + return 0; + } + + default: + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/pandatool/src/win-stats/winStatsLabelStack.h b/pandatool/src/win-stats/winStatsLabelStack.h new file mode 100644 index 00000000..32239006 --- /dev/null +++ b/pandatool/src/win-stats/winStatsLabelStack.h @@ -0,0 +1,91 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsLabelStack.h + * @author drose + * @date 2004-01-07 + */ + +#ifndef WINSTATSLABELSTACK_H +#define WINSTATSLABELSTACK_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "vector_int.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsLabel; +class WinStatsMonitor; +class WinStatsGraph; + +/** + * A window that contains a stack of labels from bottom to top. + */ +class WinStatsLabelStack { +public: + WinStatsLabelStack(); + ~WinStatsLabelStack(); + + void setup(HWND parent_window); + bool is_setup() const; + void set_pos(int x, int y, int width, int height, + int top_margin, int bottom_margin); + + int get_x() const; + int get_y() const; + int get_width() const; + int get_height() const; + int get_ideal_width() const; + + int get_label_y(int label_index) const; + int get_label_height(int label_index) const; + int get_label_collector_index(int label_index) const; + + void clear_labels(); + int add_label(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, int collector_index, bool use_fullname); + void replace_labels(WinStatsMonitor *monitor, WinStatsGraph *graph, + int thread_index, const vector_int &collector_indices, + bool use_fullname); + int get_num_labels() const; + + void highlight_label(int collector_index); + void update_label_color(int collector_index); + +private: + void recalculate_label_positions(); + + void create_window(HWND parent_window); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + HWND _window; + int _x; + int _y; + int _width; + int _height; + int _ideal_width; + int _highlight_label; + int _top_margin = 0; + int _bottom_margin = 0; + int _scroll = 0; + + typedef pvector Labels; + Labels _labels; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsMenuId.h b/pandatool/src/win-stats/winStatsMenuId.h new file mode 100644 index 00000000..9f73b50e --- /dev/null +++ b/pandatool/src/win-stats/winStatsMenuId.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsMenuId.h + * @author drose + * @date 2004-01-11 + */ + +#ifndef WINSTATSMENUID_H +#define WINSTATSMENUID_H + +#include "pandatoolbase.h" + +/** + * The enumerated values here are used for menu ID's for the various pulldown + * menus in the application. + */ +enum WinStatsMenuId { + MI_none, + + MI_session_new, + MI_session_open, + MI_session_open_last, + MI_session_save, + MI_session_close, + MI_session_export_json, + MI_exit, + + MI_time_ms, + MI_time_hz, + MI_frame_rate_label, + MI_speed_1, + MI_speed_2, + MI_speed_3, + MI_speed_6, + MI_speed_12, + MI_pause, + + MI_graphs_close_all, + MI_graphs_reopen_default, + MI_graphs_save_default, + + // This one is last and represents the beginning of the range for the + // various "new chart" menu options. + MI_new_chart +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsMonitor.I b/pandatool/src/win-stats/winStatsMonitor.I new file mode 100644 index 00000000..6658a5ab --- /dev/null +++ b/pandatool/src/win-stats/winStatsMonitor.I @@ -0,0 +1,41 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsMonitor.I + * @author drose + * @date 2004-01-09 + */ + +/** + * + */ +INLINE WinStatsMonitor::MenuDef:: +MenuDef(int thread_index, int collector_index, ChartType chart_type, bool show_level) : + _thread_index(thread_index), + _collector_index(collector_index), + _chart_type(chart_type), + _show_level(show_level) +{ +} + +/** + * + */ +INLINE bool WinStatsMonitor::MenuDef:: +operator < (const MenuDef &other) const { + if (_thread_index != other._thread_index) { + return _thread_index < other._thread_index; + } + if (_collector_index != other._collector_index) { + return _collector_index < other._collector_index; + } + if (_chart_type != other._chart_type) { + return _chart_type < other._chart_type; + } + return (int)_show_level < (int)other._show_level; +} diff --git a/pandatool/src/win-stats/winStatsMonitor.cxx b/pandatool/src/win-stats/winStatsMonitor.cxx new file mode 100644 index 00000000..410d6388 --- /dev/null +++ b/pandatool/src/win-stats/winStatsMonitor.cxx @@ -0,0 +1,952 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsMonitor.cxx + * @author drose + * @date 2003-12-02 + */ + +#include "winStatsMonitor.h" +#include "winStatsServer.h" +#include "winStatsStripChart.h" +#include "winStatsPianoRoll.h" +#include "winStatsFlameGraph.h" +#include "winStatsTimeline.h" +#include "winStatsChartMenu.h" +#include "winStatsMenuId.h" +#include "pStatFrameData.h" +#include "pStatGraph.h" +#include "pStatCollectorDef.h" + +#include "convert_srgb.h" + +#include + +#include +#include +#include + +/** + * + */ +WinStatsMonitor:: +WinStatsMonitor(WinStatsServer *server) : PStatMonitor(server) { + _window = server->get_window(); + _menu_bar = server->get_menu_bar(); + _status_bar = server->get_status_bar(); + + // These will be filled in later when the menu is created. + _scroll_speed = 0.0; + _pause = false; + + setup_speed_menu(); + setup_frame_rate_label(); +} + +/** + * + */ +WinStatsMonitor:: +~WinStatsMonitor() { + close(); +} + +/** + * Closes the client connection if it is active. + */ +void WinStatsMonitor:: +close() { + PStatMonitor::close(); + + remove_all_graphs(); + + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + + for (WinStatsChartMenu *chart_menu : _chart_menus) { + RemoveMenu(_menu_bar, 2, MF_BYPOSITION); + delete chart_menu; + } + _chart_menus.clear(); + + DrawMenuBar(_window); +} + +/** + * Should be redefined to return a descriptive name for the type of + * PStatsMonitor this is. + */ +std::string WinStatsMonitor:: +get_monitor_name() { + return "WinStats"; +} + +/** + * Called after the monitor has been fully set up. At this time, it will have + * a valid _client_data pointer, and things like is_alive() and close() will + * be meaningful. However, we may not yet know who we're connected to + * (is_client_known() may return false), and we may not know anything about + * the threads or collectors we're about to get data on. + */ +void WinStatsMonitor:: +initialized() { +} + +/** + * Called when the "hello" message has been received from the client. At this + * time, the client's hostname and program name will be known. + */ +void WinStatsMonitor:: +got_hello() { +} + +/** + * Like got_hello(), this is called when the "hello" message has been received + * from the client. At this time, the client's hostname and program name will + * be known. However, the client appears to be an incompatible version and + * the connection will be terminated; the monitor should issue a message to + * that effect. + */ +void WinStatsMonitor:: +got_bad_version(int client_major, int client_minor, + int server_major, int server_minor) { + std::ostringstream str; + str << "Unable to honor connection attempt from " + << get_client_progname() << " on " << get_client_hostname() + << ": unsupported PStats version " + << client_major << "." << client_minor; + + if (server_minor == 0) { + str << " (server understands version " << server_major + << "." << server_minor << " only)."; + } else { + str << " (server understands versions " << server_major + << ".0 through " << server_major << "." << server_minor << ")."; + } + + std::string message = str.str(); + MessageBox(nullptr, message.c_str(), "Bad version", + MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND); +} + +/** + * Called whenever a new Collector definition is received from the client. + * Generally, the client will send all of its collectors over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Collector definitions midstream. + */ +void WinStatsMonitor:: +new_collector(int collector_index) { + for (WinStatsGraph *graph : _graphs) { + graph->new_collector(collector_index); + } +} + +/** + * Called whenever a new Thread definition is received from the client. + * Generally, the client will send all of its threads over shortly after + * connecting, but there's no guarantee that they will all be received before + * the first frames are received. The monitor should be prepared to accept + * new Thread definitions midstream. + */ +void WinStatsMonitor:: +new_thread(int thread_index) { + WinStatsChartMenu *chart_menu = new WinStatsChartMenu(this, thread_index); + chart_menu->add_to_menu_bar(_menu_bar, MI_frame_rate_label); + _chart_menus.push_back(chart_menu); + DrawMenuBar(_window); + + if (thread_index == 0) { + update_status_bar(); + } +} + +/** + * Called when a thread should be removed from the list of threads. + */ +void WinStatsMonitor:: +remove_thread(int thread_index) { + for (ChartMenus::iterator it = _chart_menus.begin(); it != _chart_menus.end(); ++it) { + WinStatsChartMenu *chart_menu = *it; + if (chart_menu->get_thread_index() == thread_index) { + chart_menu->remove_from_menu_bar(_menu_bar); + delete chart_menu; + _chart_menus.erase(it); + return; + } + } +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void WinStatsMonitor:: +new_data(int thread_index, int frame_number) { + PStatMonitor::new_data(thread_index, frame_number); + + for (WinStatsGraph *graph : _graphs) { + graph->new_data(thread_index, frame_number); + } + + if (thread_index == 0) { + update_status_bar(); + } + + if (!_have_data) { + open_default_graphs(); + _have_data = true; + } +} + +/** + * Called whenever the connection to the client has been lost. This is a + * permanent state change. The monitor should update its display to represent + * this, and may choose to close down automatically. + */ +void WinStatsMonitor:: +lost_connection() { + nout << "Lost connection to " << get_client_hostname() << "\n"; +} + +/** + * If has_idle() returns true, this will be called periodically to allow the + * monitor to update its display or whatever it needs to do. + */ +void WinStatsMonitor:: +idle() { + // Check if any of our chart menus need updating. + for (WinStatsChartMenu *chart_menu : _chart_menus) { + chart_menu->check_update(); + } + + // Update the frame rate label from the main thread (thread 0). + const PStatThreadData *thread_data = get_client_data()->get_thread_data(0); + double frame_rate = thread_data->get_frame_rate(); + if (frame_rate != 0.0f) { + // The leading tab centers the text in the status bar. + char buffer[128]; + sprintf(buffer, "\t%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STRING; + mii.dwTypeData = buffer + 1; // chop off leading tab + SetMenuItemInfo(_menu_bar, MI_frame_rate_label, FALSE, &mii); + DrawMenuBar(_window); + + if (_status_bar) { + SendMessage(_status_bar, WM_SETTEXT, 0, (LPARAM)buffer); + } + } +} + +/** + * Should be redefined to return true if you want to redefine idle() and + * expect it to be called. + */ +bool WinStatsMonitor:: +has_idle() { + return true; +} + +/** + * Called when the user guide bars have been changed. + */ +void WinStatsMonitor:: +user_guide_bars_changed() { + for (WinStatsGraph *graph : _graphs) { + graph->user_guide_bars_changed(); + } +} + +/** + * Returns the window handle to the monitor's window. + */ +HWND WinStatsMonitor:: +get_window() const { + return _window; +} + +/** + * Returns the font that should be used for rendering text. + */ +HFONT WinStatsMonitor:: +get_font() const { + return ((WinStatsServer *)_server)->get_font(); +} + +/** + * Returns the system DPI scaling as a fraction where 4 = no scaling. + */ +int WinStatsMonitor:: +get_pixel_scale() const { + return ((WinStatsServer *)_server)->get_pixel_scale(); +} + +/** + * Returns an amount by which to offset the next window position. + */ +POINT WinStatsMonitor:: +get_new_window_pos() { + WinStatsServer *server = (WinStatsServer *)_server; + int offset = _graphs.size() * 10 * server->get_pixel_scale(); + POINT client_origin = server->get_client_origin(); + POINT pt; + pt.x = offset + client_origin.x; + pt.y = offset + client_origin.y; + return pt; +} + +/** + * Opens a new timeline. + */ +PStatGraph *WinStatsMonitor:: +open_timeline() { + WinStatsTimeline *graph = new WinStatsTimeline(this); + add_graph(graph); + return graph; +} + +/** + * Opens a new strip chart showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_strip_chart(int thread_index, int collector_index, bool show_level) { + WinStatsStripChart *graph = + new WinStatsStripChart(this, thread_index, collector_index, show_level); + add_graph(graph); + return graph; +} + +/** + * Opens a new flame graph showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_flame_graph(int thread_index, int collector_index, int frame_number) { + WinStatsFlameGraph *graph = new WinStatsFlameGraph(this, thread_index, collector_index, frame_number); + add_graph(graph); + return graph; +} + +/** + * Opens a new piano roll showing the indicated data. + */ +PStatGraph *WinStatsMonitor:: +open_piano_roll(int thread_index) { + WinStatsPianoRoll *graph = new WinStatsPianoRoll(this, thread_index); + add_graph(graph); + return graph; +} + +/** + * Opens a dialog to change the given collector color. + */ +void WinStatsMonitor:: +choose_collector_color(int collector_index) { + const LRGBColor ¤t = get_collector_color(collector_index); + static COLORREF custom_colors[16] = {0}; + + CHOOSECOLORA cc = { + sizeof(CHOOSECOLORA), + _window, + 0, + RGB(encode_sRGB_uchar((float)current[0]), + encode_sRGB_uchar((float)current[1]), + encode_sRGB_uchar((float)current[2])), + (LPDWORD)custom_colors, + CC_FULLOPEN | CC_RGBINIT, + 0, + nullptr, + nullptr, + }; + + if (ChooseColorA(&cc)) { + LRGBColor result( + decode_sRGB_float(GetRValue(cc.rgbResult)), + decode_sRGB_float(GetGValue(cc.rgbResult)), + decode_sRGB_float(GetBValue(cc.rgbResult))); + + set_collector_color(collector_index, result); + + for (WinStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } + } +} + +/** + * Resets the color of the given collector to the default. + */ +void WinStatsMonitor:: +reset_collector_color(int collector_index) { + clear_collector_color(collector_index); + + for (WinStatsGraph *graph : _graphs) { + graph->reset_collector_color(collector_index); + } +} + +/** + * Returns the MenuDef properties associated with the indicated menu ID. This + * specifies what we expect to do when the given menu has been selected. + */ +const WinStatsMonitor::MenuDef &WinStatsMonitor:: +lookup_menu(int menu_id) const { + static MenuDef invalid(0, 0, CT_strip_chart, false); + int menu_index = menu_id - MI_new_chart; + nassertr(menu_index >= 0 && menu_index < (int)_menu_by_id.size(), invalid); + return _menu_by_id[menu_index]; +} + +/** + * Returns the menu ID that is reserved for the indicated MenuDef properties. + * If this is the first time these particular properties have been requested, + * a new menu ID is returned; otherwise, the existing menu ID is returned. + */ +int WinStatsMonitor:: +get_menu_id(const MenuDef &menu_def) { + MenuByDef::iterator mi; + mi = _menu_by_def.find(menu_def); + if (mi != _menu_by_def.end()) { + return (*mi).second; + } + + // Slot a new id. + int menu_id = (int)_menu_by_id.size() + MI_new_chart; + _menu_by_id.push_back(menu_def); + _menu_by_def[menu_def] = menu_id; + + return menu_id; +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void WinStatsMonitor:: +set_time_units(int unit_mask) { + for (WinStatsGraph *graph : _graphs) { + graph->set_time_units(unit_mask); + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speeds for all graphs to the indicated value. + */ +void WinStatsMonitor:: +set_scroll_speed(double scroll_speed) { + _scroll_speed = scroll_speed; + + // First, change all of the open graphs appropriately. + for (WinStatsGraph *graph : _graphs) { + graph->set_scroll_speed(_scroll_speed); + } + + // Now change the checkmark on the pulldown menu. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + + mii.fState = IS_THRESHOLD_EQUAL(_scroll_speed, 1.0, 0.1) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_speed_1, FALSE, &mii); + + mii.fState = IS_THRESHOLD_EQUAL(_scroll_speed, 2.0, 0.1) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_speed_2, FALSE, &mii); + + mii.fState = IS_THRESHOLD_EQUAL(_scroll_speed, 3.0, 0.1) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_speed_3, FALSE, &mii); + + mii.fState = IS_THRESHOLD_EQUAL(_scroll_speed, 6.0, 0.1) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_speed_6, FALSE, &mii); + + mii.fState = IS_THRESHOLD_EQUAL(_scroll_speed, 12.0, 0.1) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_speed_12, FALSE, &mii); +} + +/** + * Called when the user selects a pause on or pause off option from the menu. + */ +void WinStatsMonitor:: +set_pause(bool pause) { + _pause = pause; + + // First, change all of the open graphs appropriately. + for (WinStatsGraph *graph : _graphs) { + graph->set_pause(_pause); + } + + // Now change the checkmark on the pulldown menu. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + + mii.fState = _pause ? MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_speed_menu, MI_pause, FALSE, &mii); +} + +/** + * Adds the newly-created graph to the list of managed graphs. + */ +void WinStatsMonitor:: +add_graph(WinStatsGraph *graph) { + _graphs.insert(graph); + + graph->set_time_units(((WinStatsServer *)_server)->get_time_units()); + graph->set_scroll_speed(_scroll_speed); + graph->set_pause(_pause); +} + +/** + * Deletes the indicated graph. + */ +void WinStatsMonitor:: +remove_graph(WinStatsGraph *graph) { + Graphs::iterator gi = _graphs.find(graph); + if (gi != _graphs.end()) { + _graphs.erase(gi); + delete graph; + } +} + +/** + * Deletes all open graphs. + */ +void WinStatsMonitor:: +remove_all_graphs() { + for (WinStatsGraph *graph : _graphs) { + delete graph; + } + _graphs.clear(); +} + +/** + * Creates the "Speed" pulldown menu. + */ +void WinStatsMonitor:: +setup_speed_menu() { + _speed_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _speed_menu; + mii.dwTypeData = "Speed"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE; + mii.fType = MFT_STRING | MFT_RADIOCHECK; + mii.hbmpChecked = nullptr; + mii.hbmpUnchecked = nullptr; + mii.fState = MFS_UNCHECKED; + mii.wID = MI_speed_1; + mii.dwTypeData = "1"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.wID = MI_speed_2; + mii.dwTypeData = "2"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.wID = MI_speed_3; + mii.dwTypeData = "3"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.wID = MI_speed_6; + mii.dwTypeData = "6"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.wID = MI_speed_12; + mii.dwTypeData = "12"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE; + mii.fType = MFT_STRING; + mii.wID = MI_pause; + mii.dwTypeData = "pause"; + InsertMenuItem(_speed_menu, GetMenuItemCount(_speed_menu), TRUE, &mii); + + set_scroll_speed(3); + set_pause(false); +} + +/** + * Creates the frame rate label on the right end of the menu bar. This is + * used as a text label to display the main thread's frame rate to the user, + * although it is implemented as a right-justified toplevel menu item that + * doesn't open to anything. + */ +void WinStatsMonitor:: +setup_frame_rate_label() { + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + mii.fType = MFT_STRING | MFT_RIGHTJUSTIFY; + mii.wID = MI_frame_rate_label; + mii.dwTypeData = ""; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); +} + +/** + * Updates the status bar. + */ +void WinStatsMonitor:: +update_status_bar() { + const PStatClientData *client_data = get_client_data(); + if (client_data == nullptr) { + return; + } + + const PStatThreadData *thread_data = get_client_data()->get_thread_data(0); + if (thread_data == nullptr || thread_data->is_empty()) { + return; + } + int frame_number = thread_data->get_latest_frame_number(); + const PStatFrameData &frame_data = thread_data->get_latest_frame(); + + // Gather the top-level collector list. + pvector parts; + pvector collectors; + size_t total_chars = 0; + + int num_toplevel_collectors = client_data->get_num_toplevel_collectors(); + for (int tc = 0; tc < num_toplevel_collectors; tc++) { + int collector = client_data->get_toplevel_collector(tc); + if (client_data->has_collector(collector) && + client_data->get_collector_has_level(collector, 0)) { + PStatView &view = get_level_view(collector, 0); + view.set_to_frame(frame_data); + double value = view.get_net_value(); + if (value == 0.0) { + // Don't include it unless we've included it before. + if (std::find(_status_bar_collectors.begin(), _status_bar_collectors.end(), collector) == _status_bar_collectors.end()) { + continue; + } + } + + // Add the value for other threads that have this collector. + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + view.set_to_frame(frame_number); + value += view.get_net_value(); + } + + const PStatCollectorDef &def = client_data->get_collector_def(collector); + std::string text = "\t" + def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + total_chars += text.size(); + parts.push_back(text); + collectors.push_back(collector); + } + } + + size_t cur_size = _status_bar_collectors.size(); + _status_bar_collectors = std::move(collectors); + + // Allocate an array for holding the right edge coordinates. + HLOCAL hloc = LocalAlloc(LHND, sizeof(int) * (parts.size() + 1)); + PINT sizes = (PINT)LocalLock(hloc); + + int pixel_scale = get_pixel_scale(); + + // Allocate the left-most slot for the framerate indicator. + double offset = 28.0 * pixel_scale; + sizes[0] = (int)(offset + 0.5); + + if (!parts.empty()) { + // Distribute the sizes roughly based on the number of characters. It's not + // as good as measuring the text, but it's good enough. + RECT rect; + double width_per_char = 0; + GetClientRect(_status_bar, &rect); + // Leave room for the grip. + rect.right -= pixel_scale * 4; + width_per_char = (rect.right - rect.left - offset) / (double)total_chars; + + // If we get below a minimum width, start chopping parts off. + while (!parts.empty() && width_per_char < pixel_scale * 1.5) { + total_chars -= parts.back().size(); + parts.pop_back(); + width_per_char = (rect.right - rect.left - offset) / (double)total_chars; + } + + if (!parts.empty()) { + for (size_t i = 0; i < parts.size(); ++i) { + offset += (parts[i].size()) * width_per_char; + sizes[i + 1] = (int)(offset + 0.5); + } + } else { + // No room for any collectors; the framerate can take up the whole width. + sizes[0] = rect.right - rect.left; + } + } + + SendMessage(_status_bar, SB_SETPARTS, (WPARAM)(parts.size() + 1), (LPARAM)sizes); + + LocalUnlock(hloc); + LocalFree(hloc); + + for (size_t i = 0; i < parts.size(); ++i) { + SendMessage(_status_bar, SB_SETTEXT, i + 1, (LPARAM)parts[i].c_str()); + } +} + +/** + * Called when someone right-clicks on a part of the status bar. + */ +void WinStatsMonitor:: +show_popup_menu(int collector) { + POINT point; + if (!GetCursorPos(&point)) { + return; + } + + const PStatClientData *client_data = get_client_data(); + if (client_data == nullptr) { + return; + } + + PStatView &level_view = get_level_view(collector, 0); + const PStatViewLevel *view_level = level_view.get_top_level(); + int num_children = view_level->get_num_children(); + if (num_children == 0) { + return; + } + + HMENU popup = CreatePopupMenu(); + + // Reverse the order since the menus are listed from the top down; we want + // to be visually consistent with the graphs, which list these labels from + // the bottom up. + for (int c = num_children - 1; c >= 0; c--) { + const PStatViewLevel *child_level = view_level->get_child(c); + + int child_collector = child_level->get_collector(); + MenuDef menu_def(0, child_collector, CT_strip_chart, true); + int menu_id = get_menu_id(menu_def); + + double value = child_level->get_net_value(); + + const PStatCollectorDef &def = client_data->get_collector_def(child_collector); + std::string text = def._name; + text += ": " + PStatGraph::format_number(value, PStatGraph::GBU_named | PStatGraph::GBU_show_units, def._level_units); + AppendMenu(popup, MF_STRING, menu_id, text.c_str()); + } + + TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); +} + +/** + * Called when a graph window is iconified or moved in the iconified state. + */ +void WinStatsMonitor:: +calc_iconic_graph_window_pos(WinStatsGraph *moved_graph, int &x, int &y) { + MINIMIZEDMETRICS metrics; + metrics.cbSize = sizeof(metrics); + SystemParametersInfoA(SPI_GETMINIMIZEDMETRICS, 0, &metrics, 0); + + int height = GetThemeSysSize(nullptr, SM_CYSIZE) + GetThemeSysSize(nullptr, SM_CXPADDEDBORDER) * 2; + + RECT client_rect; + GetClientRect(_window, &client_rect); + MapWindowPoints(_window, nullptr, (POINT *)&client_rect, 2); + + // Remove the status bar from the client rectangle. + RECT status_bar_rect; + GetWindowRect(_status_bar, &status_bar_rect); + client_rect.bottom -= (status_bar_rect.bottom - status_bar_rect.top); + + int iconic_offset = 0; + + for (WinStatsGraph *graph : _graphs) { + RECT child_rect; + HWND window = graph->get_window(); + if (graph == moved_graph) { + x = client_rect.left + iconic_offset; + y = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + } + else if (IsIconic(window) && GetWindowRect(window, &child_rect)) { + // Keep it glued to the bottom-left corner of the parent window. + child_rect.left = client_rect.left + iconic_offset; + child_rect.top = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + + SetWindowPos(window, 0, child_rect.left, child_rect.top, 0, 0, + SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + } + } +} + +/** + * Called when the server window is moved. + */ +void WinStatsMonitor:: +handle_window_moved(const RECT &client_rect, int delta_x, int delta_y) { + if (_graphs.empty()) { + return; + } + + MINIMIZEDMETRICS metrics; + metrics.cbSize = sizeof(metrics); + SystemParametersInfoA(SPI_GETMINIMIZEDMETRICS, 0, &metrics, 0); + + int height = GetThemeSysSize(nullptr, SM_CYSIZE) + GetThemeSysSize(nullptr, SM_CXPADDEDBORDER) * 2; + + int iconic_offset = 0; + + for (WinStatsGraph *graph : _graphs) { + WINDOWPLACEMENT wp; + HWND window = graph->get_window(); + if (GetWindowPlacement(window, &wp)) { + if (wp.showCmd == SW_SHOWMINIMIZED) { + // Keep the minimized window title bar glued to the bottom-left + // corner of the parent window. + wp.flags |= WPF_SETMINPOSITION; + wp.ptMinPosition.x = client_rect.left + iconic_offset; + wp.ptMinPosition.y = client_rect.bottom - height; + iconic_offset += metrics.iWidth + metrics.iHorzGap; + } + + // Move the "restored" window (even when it's currently min/maximized). + wp.rcNormalPosition.left += delta_x; + wp.rcNormalPosition.top += delta_y; + wp.rcNormalPosition.right += delta_x; + wp.rcNormalPosition.bottom += delta_y; + SetWindowPlacement(window, &wp); + } + } +} + +/** + * Called when a menu item is clicked. + */ +void WinStatsMonitor:: +handle_menu_command(int menu_id) { + switch (menu_id) { + case MI_none: + break; + + case MI_speed_1: + set_scroll_speed(1); + break; + + case MI_speed_2: + set_scroll_speed(2); + break; + + case MI_speed_3: + set_scroll_speed(3); + break; + + case MI_speed_6: + set_scroll_speed(6); + break; + + case MI_speed_12: + set_scroll_speed(12); + break; + + case MI_pause: + set_pause(!_pause); + break; + + case MI_graphs_close_all: + remove_all_graphs(); + break; + + case MI_graphs_reopen_default: + remove_all_graphs(); + open_default_graphs(); + break; + + case MI_graphs_save_default: + save_default_graphs(); + break; + + default: + if (menu_id >= MI_new_chart) { + const MenuDef &menu_def = lookup_menu(menu_id); + switch (menu_def._chart_type) { + case CT_timeline: + open_timeline(); + break; + + case CT_strip_chart: + open_strip_chart(menu_def._thread_index, menu_def._collector_index, + menu_def._show_level); + break; + + case CT_flame_graph: + open_flame_graph(menu_def._thread_index, menu_def._collector_index); + break; + + case CT_piano_roll: + open_piano_roll(menu_def._thread_index); + break; + } + } + } +} + +/** + * Called when a status bar item is double-clicked. + */ +void WinStatsMonitor:: +handle_status_bar_click(int item) { + if (item == 0) { + open_strip_chart(0, 0, false); + } + else if (item >= 1 && item <= _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item - 1]; + open_strip_chart(0, collector, true); + + // Also open a strip chart for other threads with data for this + // collector. + const PStatClientData *client_data = get_client_data(); + for (int thread_index = 1; thread_index < client_data->get_num_threads(); ++thread_index) { + PStatView &view = get_level_view(collector, thread_index); + if (view.get_net_value() > 0.0) { + open_strip_chart(thread_index, collector, true); + } + } + } +} + +/** + * Called when a status bar item is right-clicked. + */ +void WinStatsMonitor:: +handle_status_bar_popup(int item) { + if (item >= 1 && item <= _status_bar_collectors.size()) { + int collector = _status_bar_collectors[item - 1]; + show_popup_menu(collector); + } +} diff --git a/pandatool/src/win-stats/winStatsMonitor.h b/pandatool/src/win-stats/winStatsMonitor.h new file mode 100644 index 00000000..1fed3f9a --- /dev/null +++ b/pandatool/src/win-stats/winStatsMonitor.h @@ -0,0 +1,143 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsMonitor.h + * @author drose + * @date 2003-12-02 + */ + +#ifndef WINSTATSMONITOR_H +#define WINSTATSMONITOR_H + +#include "pandatoolbase.h" + +#include "winStatsGraph.h" +#include "pStatMonitor.h" +#include "pointerTo.h" +#include "pset.h" +#include "pvector.h" +#include "pmap.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsServer; +class WinStatsChartMenu; + +/** + * This class represents a connection to a PStatsClient and manages the data + * exchange with the client. + */ +class WinStatsMonitor : public PStatMonitor { +public: + enum ChartType { + CT_timeline, + CT_strip_chart, + CT_flame_graph, + CT_piano_roll, + }; + + class MenuDef { + public: + INLINE MenuDef(int thread_index, int collector_index, + ChartType chart_type, bool show_level = false); + INLINE bool operator < (const MenuDef &other) const; + + int _thread_index; + int _collector_index; + ChartType _chart_type; + bool _show_level; + }; + + WinStatsMonitor(WinStatsServer *server); + virtual ~WinStatsMonitor(); + + void close(); + + virtual std::string get_monitor_name(); + + virtual void initialized(); + virtual void got_hello(); + virtual void got_bad_version(int client_major, int client_minor, + int server_major, int server_minor); + virtual void new_collector(int collector_index); + virtual void new_thread(int thread_index); + virtual void new_data(int thread_index, int frame_number); + virtual void remove_thread(int thread_index); + virtual void lost_connection(); + virtual void idle(); + virtual bool has_idle(); + + virtual void user_guide_bars_changed(); + + HWND get_window() const; + HFONT get_font() const; + int get_pixel_scale() const; + POINT get_new_window_pos(); + + PStatGraph *open_timeline(); + PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level); + PStatGraph *open_flame_graph(int thread_index, int collector_index = -1, int frame_number = -1); + PStatGraph *open_piano_roll(int thread_index); + + void choose_collector_color(int collector_index); + void reset_collector_color(int collector_index); + + const MenuDef &lookup_menu(int menu_id) const; + int get_menu_id(const MenuDef &menu_def); + + void set_time_units(int unit_mask); + void set_scroll_speed(double scroll_speed); + void set_pause(bool pause); + + void add_graph(WinStatsGraph *graph); + void remove_graph(WinStatsGraph *graph); + void remove_all_graphs(); + +private: + void setup_speed_menu(); + void setup_frame_rate_label(); + void update_status_bar(); + void show_popup_menu(int collector); + + void calc_iconic_graph_window_pos(WinStatsGraph *graph, int &x, int &y); + void handle_window_moved(const RECT &client_rect, int delta_x, int delta_y); + void handle_menu_command(int menu_id); + void handle_status_bar_click(int item); + void handle_status_bar_popup(int item); + + typedef pset Graphs; + Graphs _graphs; + + typedef pvector ChartMenus; + ChartMenus _chart_menus; + + typedef pvector MenuById; + typedef pmap MenuByDef; + MenuById _menu_by_id; + MenuByDef _menu_by_def; + + HWND _window; + HMENU _menu_bar; + HMENU _speed_menu; + HWND _status_bar; + pvector _status_bar_collectors; + std::string _window_title; + double _scroll_speed; + bool _pause; + bool _have_data = false; + + friend class WinStatsGraph; + friend class WinStatsServer; +}; + +#include "winStatsMonitor.I" + +#endif diff --git a/pandatool/src/win-stats/winStatsPianoRoll.cxx b/pandatool/src/win-stats/winStatsPianoRoll.cxx new file mode 100644 index 00000000..97cbbf88 --- /dev/null +++ b/pandatool/src/win-stats/winStatsPianoRoll.cxx @@ -0,0 +1,699 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsPianoRoll.cxx + * @author drose + * @date 2003-12-03 + */ + +#include "winStatsPianoRoll.h" +#include "winStatsMonitor.h" +#include "numeric_types.h" + +static const int default_piano_roll_width = 800; +static const int default_piano_roll_height = 400; + +bool WinStatsPianoRoll::_window_class_registered = false; +const char * const WinStatsPianoRoll::_window_class_name = "piano"; + +/** + * + */ +WinStatsPianoRoll:: +WinStatsPianoRoll(WinStatsMonitor *monitor, int thread_index) : + PStatPianoRoll(monitor, thread_index, + monitor->get_pixel_scale() * default_piano_roll_width / 4, + monitor->get_pixel_scale() * default_piano_roll_height / 4), + WinStatsGraph(monitor) +{ + _left_margin = _pixel_scale * 32; + _right_margin = _pixel_scale * 2; + _top_margin = _pixel_scale * 5; + _bottom_margin = _pixel_scale * 2; + _top_label_stack_margin = _pixel_scale * 5; + + // Let's show the units on the guide bar labels. There's room. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + + create_window(); + force_redraw(); + idle(); +} + +/** + * + */ +WinStatsPianoRoll:: +~WinStatsPianoRoll() { +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void WinStatsPianoRoll:: +new_data(int thread_index, int frame_number) { + if (!_pause) { + update(); + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void WinStatsPianoRoll:: +force_redraw() { + PStatPianoRoll::force_redraw(); +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void WinStatsPianoRoll:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatPianoRoll::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void WinStatsPianoRoll:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + RECT rect; + GetClientRect(_window, &rect); + rect.left = _right_margin; + InvalidateRect(_window, &rect, TRUE); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void WinStatsPianoRoll:: +on_click_label(int collector_index) { + if (collector_index >= 0) { + WinStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false); + } +} + +/** + * Called when the user right-clicks on a label. + */ +void WinStatsPianoRoll:: +on_popup_label(int collector_index) { + POINT point; + if (collector_index >= 0 && GetCursorPos(&point)) { + _popup_index = collector_index; + + HMENU popup = CreatePopupMenu(); + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 0, label.c_str()); + } + AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); + AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); + TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); + } +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsPianoRoll:: +get_label_tooltip(int collector_index) const { + return PStatPianoRoll::get_label_tooltip(collector_index); +} + +/** + * Changes the amount of time the width of the horizontal axis represents. + * This may force a redraw. + */ +void WinStatsPianoRoll:: +set_horizontal_scale(double time_width) { + PStatPianoRoll::set_horizontal_scale(time_width); + + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); +} + +/** + * Calls update_guide_bars with parameters suitable to this kind of graph. + */ +void WinStatsPianoRoll:: +normal_guide_bars() { + // We want vaguely 100 pixels between guide bars. + update_guide_bars(get_xsize() / (_pixel_scale * 25), get_horizontal_scale()); +} + +/** + * Erases the chart area. + */ +void WinStatsPianoRoll:: +clear_region() { + RECT rect = { 0, 0, get_xsize(), get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void WinStatsPianoRoll:: +begin_draw() { + clear_region(); + + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_bitmap_dc, get_guide_bar(i)); + } + + SelectObject(_bitmap_dc, GetStockObject(NULL_PEN)); +} + +/** + * Should be overridden by the user class. This hook will be called before + * drawing any one row of bars. These bars correspond to the collector whose + * index is get_row_collector(row), and in the color get_row_color(row). + */ +void WinStatsPianoRoll:: +begin_row(int row) { + int collector_index = get_label_collector(row); + HBRUSH brush = get_collector_brush(collector_index, _highlighted_index == collector_index); + SelectObject(_bitmap_dc, brush); + SelectObject(_bitmap_dc, GetStockObject(NULL_PEN)); +} + +/** + * Draws a single bar on the chart. + */ +void WinStatsPianoRoll:: +draw_bar(int row, int from_x, int to_x) { + if (row >= 0 && row < _label_stack.get_num_labels()) { + int y = _label_stack.get_label_y(row) - _graph_top; + int height = _label_stack.get_label_height(row); + + RoundRect(_bitmap_dc, from_x, y - height + 2, to_x, y - 2, _pixel_scale, _pixel_scale); + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void WinStatsPianoRoll:: +end_draw() { + InvalidateRect(_graph_window, nullptr, FALSE); +} + +/** + * Called at the end of the draw cycle. + */ +void WinStatsPianoRoll:: +idle() { + if (_labels_changed) { + update_labels(); + } +} + +/** + * Returns the current window dimensions. + */ +bool WinStatsPianoRoll:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsPianoRoll:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * + */ +LONG WinStatsPianoRoll:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + if (_potential_drag_mode == DM_new_guide_bar) { + set_drag_mode(DM_new_guide_bar); + SetCapture(_graph_window); + return 0; + } + break; + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case 102: + WinStatsGraph::_monitor->open_strip_chart(get_thread_index(), _popup_index, false); + return 0; + + case 103: + WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); + return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; + } + break; + + default: + break; + } + + return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +LONG WinStatsPianoRoll:: +graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + int16_t x = LOWORD(lparam); + _drag_scale_start = pixel_to_height(x); + SetCapture(_graph_window); + return 0; + + } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + int16_t x = LOWORD(lparam); + _drag_start_x = x; + SetCapture(_graph_window); + return 0; + } + break; + + case WM_MOUSEMOVE: + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + + int collector_index = get_collector_under_pixel(x, y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + int16_t x = LOWORD(lparam); + double ratio = (double)x / (double)get_xsize(); + if (ratio > 0.0f) { + set_horizontal_scale(_drag_scale_start / ratio); + } + return 0; + + } else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + int16_t x = LOWORD(lparam); + if (x >= 0 && x < get_xsize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(x)); + return 0; + } + + } else if (_drag_mode == DM_guide_bar) { + int16_t x = LOWORD(lparam); + move_user_guide_bar(_drag_guide_bar, pixel_to_height(x)); + return 0; + } + break; + + case WM_MOUSELEAVE: + // When the mouse leaves the graph, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + break; + + case WM_LBUTTONUP: + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + + } else if (_drag_mode == DM_guide_bar) { + int16_t x = LOWORD(lparam); + if (x < 0 || x >= get_xsize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(x)); + } + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + } + break; + + case WM_LBUTTONDBLCLK: + { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + on_click_label(get_collector_under_pixel(x, y)); + return 0; + } + break; + + case WM_CONTEXTMENU: + { + POINT point; + if (GetCursorPos(&point) && ScreenToClient(_graph_window, &point)) { + int collector_index = get_collector_under_pixel(point.x, point.y); + if (collector_index >= 0) { + on_popup_label(collector_index); + } + } + return 0; + } + break; + + default: + break; + } + + return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsPianoRoll:: +additional_window_paint(HDC hdc) { + // Draw in the labels for the guide bars. + SelectObject(hdc, WinStatsGraph::_monitor->get_font()); + SetTextAlign(hdc, TA_LEFT | TA_BOTTOM); + SetBkMode(hdc, TRANSPARENT); + + int y = _top_margin - 2; + + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + draw_guide_label(hdc, y, get_guide_bar(i)); + } + + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + draw_guide_label(hdc, y, get_user_guide_bar(i)); + } +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsPianoRoll:: +additional_graph_window_paint(HDC hdc) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(hdc, get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsPianoRoll:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + int collector_index = get_collector_under_pixel(mouse_x, mouse_y); + if (collector_index >= 0) { + return get_label_tooltip(collector_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +WinStatsGraph::DragMode WinStatsPianoRoll:: +consider_drag_start(int mouse_x, int mouse_y, int width, int height) { + if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) { + if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) { + // See if the mouse is over a user-defined guide bar. + int x = mouse_x - _graph_left; + double from_height = pixel_to_height(x - 2); + double to_height = pixel_to_height(x + 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else if (mouse_x < _left_margin - 2 || + mouse_x > width - _right_margin + 2) { + // The mouse is left or right of the graph; maybe create a new guide + // bar. + return DM_new_guide_bar; + } + } + + return WinStatsGraph::consider_drag_start(mouse_x, mouse_y, width, height); +} + +/** + * Returns the collector index associated with the indicated vertical row, or + * -1. + */ +int WinStatsPianoRoll:: +get_collector_under_pixel(int xpoint, int ypoint) const { + if (_label_stack.get_num_labels() == 0) { + return -1; + } + + // Assume all of the labels are the same height. + int height = _label_stack.get_label_height(0); + int row = (get_ysize() - ypoint) / height; + if (row >= 0 && row < _label_stack.get_num_labels()) { + return _label_stack.get_label_collector_index(row); + } else { + return -1; + } +} + +/** + * Resets the list of labels. + */ +void WinStatsPianoRoll:: +update_labels() { + _label_stack.replace_labels(WinStatsGraph::_monitor, this, + _thread_index, _labels, true); + _labels_changed = false; +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void WinStatsPianoRoll:: +draw_guide_bar(HDC hdc, const PStatGraph::GuideBar &bar) { + int x = height_to_pixel(bar._height); + + if (x > 0 && x < get_xsize() - 1) { + // Only draw it if it's not too close to either edge. + switch (bar._style) { + case GBS_target: + SelectObject(hdc, _light_pen); + break; + + case GBS_user: + SelectObject(hdc, _user_guide_bar_pen); + break; + + case GBS_normal: + SelectObject(hdc, _dark_pen); + break; + } + MoveToEx(hdc, x, 0, nullptr); + LineTo(hdc, x, get_ysize()); + } +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void WinStatsPianoRoll:: +draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar) { + switch (bar._style) { + case GBS_target: + SetTextColor(hdc, _light_color); + break; + + case GBS_user: + SetTextColor(hdc, _user_guide_bar_color); + break; + + case GBS_normal: + SetTextColor(hdc, _dark_color); + break; + } + + int x = height_to_pixel(bar._height); + const std::string &label = bar._label; + SIZE size; + GetTextExtentPoint32(hdc, label.data(), label.length(), &size); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(x - size.cx); + double to_height = pixel_to_height(x + size.cx); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + return; + } + } + + int this_x = _graph_left + x - size.cx / 2; + if (x >= 0 && x < get_xsize()) { + TextOut(hdc, this_x, y, + label.data(), label.length()); + } +} + +/** + * Creates the window for this strip chart. + */ +void WinStatsPianoRoll:: +create_window() { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + const PStatClientData *client_data = + WinStatsGraph::_monitor->get_client_data(); + std::string thread_name = client_data->get_thread_name(_thread_index); + std::string window_title = thread_name + " thread piano roll"; + POINT window_pos = WinStatsGraph::_monitor->get_new_window_pos(); + + RECT win_rect = { + 0, 0, + _left_margin + get_xsize() + _right_margin, + _top_margin + get_ysize() + _bottom_margin + }; + + // compute window size based on desired client area size + AdjustWindowRect(&win_rect, graph_window_style, FALSE); + + _window = + CreateWindowEx(WS_EX_DLGMODALFRAME, _window_class_name, + window_title.c_str(), graph_window_style, + window_pos.x, window_pos.y, + win_rect.right - win_rect.left, + win_rect.bottom - win_rect.top, + WinStatsGraph::_monitor->get_window(), + nullptr, application, 0); + if (!_window) { + nout << "Could not create PianoRoll window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + setup_label_stack(); + + // Ensure that the window is on top of the stack. + SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); +} + +/** + * Registers the window class for the pianoRoll window, if it has not already + * been registered. + */ +void WinStatsPianoRoll:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsPianoRoll *); + + if (!RegisterClass(&wc)) { + nout << "Could not register PianoRoll window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsPianoRoll:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsPianoRoll *self = (WinStatsPianoRoll *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} diff --git a/pandatool/src/win-stats/winStatsPianoRoll.h b/pandatool/src/win-stats/winStatsPianoRoll.h new file mode 100644 index 00000000..ebf6cb4e --- /dev/null +++ b/pandatool/src/win-stats/winStatsPianoRoll.h @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsPianoRoll.h + * @author drose + * @date 2004-01-12 + */ + +#ifndef WINSTATSPIANOROLL_H +#define WINSTATSPIANOROLL_H + +#include "pandatoolbase.h" + +#include "winStatsGraph.h" +#include "pStatPianoRoll.h" +#include "pointerTo.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsMonitor; + +/** + * A window that draws a piano-roll style chart, which shows the collectors + * explicitly stopping and starting, one frame at a time. + */ +class WinStatsPianoRoll : public PStatPianoRoll, public WinStatsGraph { +public: + WinStatsPianoRoll(WinStatsMonitor *monitor, int thread_index); + virtual ~WinStatsPianoRoll(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + void set_horizontal_scale(double time_width); + +protected: + virtual void normal_guide_bars(); + void clear_region(); + virtual void begin_draw(); + virtual void begin_row(int row); + virtual void draw_bar(int row, int from_x, int to_x); + virtual void end_draw(); + virtual void idle(); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual void additional_window_paint(HDC hdc); + virtual void additional_graph_window_paint(HDC hdc); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int mouse_x, int mouse_y, + int width, int height); + +private: + int get_collector_under_pixel(int xpoint, int ypoint) const; + void update_labels(); + void draw_guide_bar(HDC hdc, const GuideBar &bar); + void draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar); + + void create_window(); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + int _popup_index = -1; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsServer.cxx b/pandatool/src/win-stats/winStatsServer.cxx new file mode 100644 index 00000000..dd77cfbf --- /dev/null +++ b/pandatool/src/win-stats/winStatsServer.cxx @@ -0,0 +1,941 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsServer.cxx + * @author drose + * @date 2003-12-02 + */ + +#include "winStatsServer.h" +#include "winStatsMenuId.h" +#include "winStatsMonitor.h" +#include "pandaVersion.h" +#include "pStatGraph.h" +#include "config_pstatclient.h" + +#include +#include + +bool WinStatsServer::_window_class_registered = false; +const char *const WinStatsServer::_window_class_name = "server"; + +/** + * + */ +WinStatsServer:: +WinStatsServer() : _port(pstats_port) { + set_program_brief("Windows PStats client"); + add_option("p", "port", 0, "", &ProgramBase::dispatch_int, nullptr, &_port); + + _last_session = Filename::expand_from( + "$USER_APPDATA/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats"); + _last_session.set_binary(); + + // Create the fonts used for rendering the UI. + NONCLIENTMETRICS metrics = {0}; + metrics.cbSize = sizeof(NONCLIENTMETRICS); + if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0)) { + _font = CreateFontIndirect(&metrics.lfMenuFont); + } else { + _font = (HFONT)GetStockObject(ANSI_VAR_FONT); + } + + create_window(); +} + +/** + * Does something with the additional arguments on the command line (after all + * the -options have been parsed). Returns true if the arguments are good, + * false otherwise. + */ +bool WinStatsServer:: +handle_args(ProgramBase::Args &args) { + if (args.empty()) { + new_session(); + return true; + } + else if (args.size() == 1) { + Filename fn = Filename::from_os_specific(args[0]); + fn.set_binary(); + WinStatsMonitor *monitor = new WinStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + std::ostringstream stream; + stream << "Failed to load session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return true; + } + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + _monitor = monitor; + return true; + } + else { + nout << "At most one filename may be specified on the command-line.\n"; + return false; + } +} + +/** + * + */ +PStatMonitor *WinStatsServer:: +make_monitor(const NetAddress &address) { + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + std::ostringstream strm; + strm << "PStats Server (connected to " << address << ")"; + std::string title = strm.str(); + SetWindowTextA(_window, title.c_str()); + + _monitor = new WinStatsMonitor(this); + return _monitor; +} + +/** + * Called when connection has been lost. + */ +void WinStatsServer:: +lost_connection(PStatMonitor *monitor) { + if (_monitor != nullptr && !_monitor->_have_data) { + // We didn't have any data yet. Just silently restart the session. + _monitor->close(); + _monitor = nullptr; + if (new_session()) { + return; + } + } else { + // Store a backup now, in case PStats crashes or something. + _last_session.make_dir(); + if (monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + } else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + stop_listening(); + + SetWindowTextA(_window, "PStats Server (disconnected)"); +} + +/** + * Starts a new session. + */ +bool WinStatsServer:: +new_session() { + if (!close_session()) { + return false; + } + + if (listen(_port)) { + { + std::ostringstream strm; + strm << "PStats Server (listening on port " << _port << ")"; + std::string title = strm.str(); + SetWindowTextA(_window, title.c_str()); + } + { + std::ostringstream strm; + strm << "Waiting for client to connect on port " << _port << "..."; + std::string title = strm.str(); + int part = -1; + SendMessage(_status_bar, SB_SETPARTS, 1, (LPARAM)&part); + SendMessage(_status_bar, WM_SETTEXT, 0, (LPARAM)title.c_str()); + } + + // Disable the "New Session" menu item. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + + // Disable the "Save Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + + // Enable the "Close Session" menu item. + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + + // Disable the "Export Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; + } + + SetWindowTextA(_window, "PStats Server"); + + std::ostringstream stream; + stream + << "Unable to open port " << _port << ". Try specifying a different " + << "port number using pstats-port in your Config file or the -p option on " + << "the command-line."; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; +} + +/** + * Offers to open an existing session. + */ +bool WinStatsServer:: +open_session() { + if (!close_session()) { + return false; + } + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + nullptr, + "PStats Session Files (*.pstats)\0*.pstats\0All Files (*.*)\0*.*\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Open Session", + OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST, + 0, + }; + + if (GetOpenFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + + WinStatsMonitor *monitor = new WinStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + std::ostringstream stream; + stream << "Failed to load session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; + } + + return false; +} + +/** + * Opens the last session, if any. + */ +bool WinStatsServer:: +open_last_session() { + if (!close_session()) { + return false; + } + + Filename fn = _last_session; + WinStatsMonitor *monitor = new WinStatsMonitor(this); + if (!monitor->read(fn)) { + delete monitor; + + std::ostringstream stream; + stream << "Failed to load session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor = monitor; + + // Enable the "New Session", "Save Session" and "Close Session" menu items. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + // If the file contained no graphs, open the default graphs. + if (monitor->_graphs.empty()) { + monitor->open_default_graphs(); + } + + return true; +} + +/** + * Offers to save the current session. + */ +bool WinStatsServer:: +save_session() { + nassertr_always(_monitor != nullptr, true); + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + 0, + "PStats Session Files\0*.pstats\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Save Session", + OFN_OVERWRITEPROMPT, + 0, + 0, + "pstats", + 0, + }; + + if (GetSaveFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_binary(); + + if (!_monitor->write(fn)) { + std::ostringstream stream; + stream << "Failed to save session file: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + _monitor->get_client_data()->clear_dirty(); + return true; + } + + return false; +} + +/** + * Offers to export the current session as a JSON file. + */ +bool WinStatsServer:: +export_session() { + nassertr_always(_monitor != nullptr, true); + + char buffer[4096]; + buffer[0] = '\0'; + + OPENFILENAMEA ofn = { + sizeof(OPENFILENAMEA), + _window, + 0, + "JSON files\0*.json\0", + nullptr, + 0, + 0, + buffer, + sizeof(buffer), + nullptr, + 0, + nullptr, + "Export Session", + OFN_OVERWRITEPROMPT, + 0, + 0, + "json", + 0, + }; + + if (GetSaveFileNameA(&ofn)) { + Filename fn = Filename::from_os_specific(buffer); + fn.set_text(); + + std::ofstream stream; + if (!fn.open_write(stream)) { + std::ostringstream stream; + stream << "Failed to open file for export: " << fn; + std::string str = stream.str(); + MessageBox(_window, str.c_str(), "PStats Error", + MB_OK | MB_ICONEXCLAMATION); + return false; + } + + int pid = _monitor->get_client_pid(); + _monitor->get_client_data()->write_json(stream, std::max(0, pid)); + stream.close(); + return true; + } + + return false; +} + +/** + * Closes the current session. + */ +bool WinStatsServer:: +close_session() { + bool wrote_last_session = false; + + if (_monitor != nullptr) { + const PStatClientData *client_data = _monitor->get_client_data(); + if (client_data != nullptr && client_data->is_dirty()) { + if (!_monitor->has_read_filename()) { + _last_session.make_dir(); + if (_monitor->write(_last_session)) { + nout << "Wrote to " << _last_session << "\n"; + wrote_last_session = true; + } + else { + nout << "Failed to write to " << _last_session << "\n"; + } + } + + int result = MessageBox(_window, + "Would you like to save the currently open session?", + "Unsaved Data", MB_YESNOCANCEL | MB_ICONQUESTION); + if (result == IDCANCEL || (result == IDYES && !save_session())) { + return false; + } + } + + _monitor->close(); + _monitor = nullptr; + } + + stop_listening(); + + SetWindowTextA(_window, "PStats Server"); + + int part = -1; + SendMessage(_status_bar, SB_SETPARTS, 1, (LPARAM)&part); + SendMessage(_status_bar, WM_SETTEXT, 0, (LPARAM)""); + + // Enable the "New Session" menu item. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_new, FALSE, &mii); + + if (wrote_last_session) { + // And the "Open Last Session" menu item. + mii.fState = MFS_ENABLED; + SetMenuItemInfoA(_session_menu, MI_session_open_last, FALSE, &mii); + } + + // Disable the "Save Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_save, FALSE, &mii); + + // Disable the "Close Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_close, FALSE, &mii); + + // Disable the "Export Session" menu item. + mii.fState = MFS_DISABLED; + SetMenuItemInfoA(_session_menu, MI_session_export_json, FALSE, &mii); + + return true; +} + +/** + * Returns the window handle to the server's window. + */ +HWND WinStatsServer:: +get_window() const { + return _window; +} + +/** + * Returns the menu handle to the server's menu bar. + */ +HMENU WinStatsServer:: +get_menu_bar() const { + return _menu_bar; +} + +/** + * Returns the window handle to the server's status bar. + */ +HWND WinStatsServer:: +get_status_bar() const { + return _status_bar; +} + +/** + * Returns the font that should be used for rendering text. + */ +HFONT WinStatsServer:: +get_font() const { + return _font; +} + +/** + * Returns the system DPI scaling as a fraction where 4 = no scaling. + */ +int WinStatsServer:: +get_pixel_scale() const { + return _pixel_scale; +} + +/** + * Returns the origin of the window's client area. + */ +POINT WinStatsServer:: +get_client_origin() const { + return _client_origin; +} + +/** + * + */ +int WinStatsServer:: +get_time_units() const { + return _time_units; +} + + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for all graphs to the indicated mask if + * it is a time-based graph. + */ +void WinStatsServer:: +set_time_units(int unit_mask) { + _time_units = unit_mask; + + if (_monitor != nullptr) { + _monitor->set_time_units(unit_mask); + } + + // Now change the checkmark on the pulldown menu. + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + + mii.fState = ((_time_units & PStatGraph::GBU_ms) != 0) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_options_menu, MI_time_ms, FALSE, &mii); + + mii.fState = ((_time_units & PStatGraph::GBU_hz) != 0) ? + MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(_options_menu, MI_time_hz, FALSE, &mii); +} + +/** + * Creates the window for this server. + */ +void WinStatsServer:: +create_window() { + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + _menu_bar = CreateMenu(); + + setup_session_menu(); + setup_options_menu(); + + DWORD window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | + WS_CLIPSIBLINGS | WS_VISIBLE; + + _window = + CreateWindow(_window_class_name, "PStats Server", window_style, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, _menu_bar, application, 0); + if (!_window) { + nout << "Could not create server window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + + create_status_bar(application); + + // For some reason, SW_SHOWNORMAL doesn't always work, but SW_RESTORE seems + // to. + ShowWindow(_window, SW_RESTORE); + SetForegroundWindow(_window); + + HDC dc = GetDC(_window); + _pixel_scale = 0; + if (dc) { + _pixel_scale = GetDeviceCaps(dc, LOGPIXELSX) / (96 / 4); + } + if (_pixel_scale <= 0) { + _pixel_scale = 4; + } + ReleaseDC(_window, dc); + + _client_origin.x = 0; + _client_origin.y = 0; + ClientToScreen(_window, &_client_origin); + + // Set up a timer to poll the pstats every so often. + SetTimer(_window, 1, 200, nullptr); +} + +/** + * Creates the "Session" pulldown menu. + */ +void WinStatsServer:: +setup_session_menu() { + _session_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _session_menu; + + mii.dwTypeData = "&Session"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + AppendMenu(_session_menu, MF_STRING, MI_session_new, "&New Session\tCtrl+N"); + AppendMenu(_session_menu, MF_STRING, MI_session_open, "&Open Session...\tCtrl+O"); + + if (_last_session.exists()) { + AppendMenu(_session_menu, MF_STRING, MI_session_open_last, "Open &Last Session"); + } else { + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_open_last, "Open &Last Session"); + } + + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_save, "&Save Session...\tCtrl+S"); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_close, "&Close Session\tCtrl+W"); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING | MF_DISABLED, MI_session_export_json, "&Export as JSON..."); + + AppendMenu(_session_menu, MF_SEPARATOR, 0, nullptr); + AppendMenu(_session_menu, MF_STRING, MI_exit, "E&xit"); +} + +/** + * Creates the "Options" pulldown menu. + */ +void WinStatsServer:: +setup_options_menu() { + _options_menu = CreatePopupMenu(); + + MENUITEMINFO mii; + memset(&mii, 0, sizeof(mii)); + mii.cbSize = sizeof(mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_SUBMENU; + mii.fType = MFT_STRING; + mii.hSubMenu = _options_menu; + + // One day, when there is more than one option here, we will actually + // present this to the user as the "Options" menu. For now, the only option + // we have is time units. mii.dwTypeData = "Options"; + mii.dwTypeData = "Units"; + InsertMenuItem(_menu_bar, GetMenuItemCount(_menu_bar), TRUE, &mii); + + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE; + mii.fType = MFT_STRING | MFT_RADIOCHECK; + mii.hbmpChecked = nullptr; + mii.hbmpUnchecked = nullptr; + mii.fState = MFS_UNCHECKED; + mii.wID = MI_time_ms; + mii.dwTypeData = "ms"; + InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); + + mii.wID = MI_time_hz; + mii.dwTypeData = "Hz"; + InsertMenuItem(_options_menu, GetMenuItemCount(_options_menu), TRUE, &mii); + + set_time_units(PStatGraph::GBU_ms); +} + +/** + * Sets up a status bar at the bottom of the screen showing assorted level + * values. + */ +void WinStatsServer:: +create_status_bar(HINSTANCE application) { + _status_bar = CreateWindow(STATUSCLASSNAME, nullptr, + SBARS_SIZEGRIP | WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, + _window, (HMENU)0, application, nullptr); + + ShowWindow(_status_bar, SW_SHOW); + UpdateWindow(_status_bar); + + InvalidateRect(_status_bar, NULL, TRUE); +} + +/** + * Registers the window class for the server window, if it has not already + * been registered. + */ +void WinStatsServer:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + HMODULE imageres = LoadLibraryExA("imageres.dll", 0, LOAD_LIBRARY_AS_DATAFILE); + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hIcon = LoadIcon(imageres, MAKEINTRESOURCE(150)); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsServer *); + + if (!RegisterClass(&wc)) { + nout << "Could not register server window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsServer:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsServer *self = (WinStatsServer *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} + +/** + * + */ +LONG WinStatsServer:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_TIMER: + poll(); + break; + + case WM_CLOSE: + if (!close_session()) { + return 0; + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_WINDOWPOSCHANGED: + { + RECT client_rect; + GetClientRect(_window, &client_rect); + MapWindowPoints(_window, nullptr, (POINT *)&client_rect, 2); + + if (_monitor != nullptr) { + // Remove the status bar from the client rectangle. + RECT status_bar_rect; + GetWindowRect(_status_bar, &status_bar_rect); + client_rect.bottom -= (status_bar_rect.bottom - status_bar_rect.top); + + int delta_x = client_rect.left - _client_origin.x; + int delta_y = client_rect.top - _client_origin.y; + _client_origin.x = client_rect.left; + _client_origin.y = client_rect.top; + + _monitor->handle_window_moved(client_rect, delta_x, delta_y); + } + else { + _client_origin.x = client_rect.left; + _client_origin.y = client_rect.top; + } + } + break; + + case WM_SIZE: + if (_status_bar) { + SendMessage(_status_bar, WM_SIZE, 0, 0); + if (_monitor != nullptr) { + _monitor->update_status_bar(); + } + } + break; + + case WM_NOTIFY: + if (_monitor != nullptr) { + if (((LPNMHDR)lparam)->code == NM_DBLCLK) { + NMMOUSE &mouse = *(NMMOUSE *)lparam; + _monitor->handle_status_bar_click(mouse.dwItemSpec); + return TRUE; + } + else if (((LPNMHDR)lparam)->code == NM_RCLICK) { + NMMOUSE &mouse = *(NMMOUSE *)lparam; + _monitor->handle_status_bar_popup(mouse.dwItemSpec); + return TRUE; + } + } + break; + + case WM_KEYDOWN: + if ((lparam & 0x40000000) == 0 && GetKeyState(VK_CONTROL) < 0) { + switch (wparam) { + case 'N': + new_session(); + return 0; + + case 'O': + open_session(); + return 0; + + case 'S': + save_session(); + return 0; + + case 'W': + close_session(); + return 0; + + default: + break; + } + } + break; + + case WM_APPCOMMAND: + switch (GET_APPCOMMAND_LPARAM(lparam)) { + case APPCOMMAND_OPEN: + open_session(); + return TRUE; + + case APPCOMMAND_SAVE: + save_session(); + return TRUE; + } + break; + + case WM_COMMAND: + if (HIWORD(wparam) <= 1) { + int menu_id = LOWORD(wparam); + handle_menu_command(menu_id); + return 0; + } + break; + + default: + break; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +void WinStatsServer:: +handle_menu_command(int menu_id) { + switch (menu_id) { + case MI_none: + break; + + case MI_session_new: + new_session(); + break; + + case MI_session_open: + open_session(); + break; + + case MI_session_open_last: + open_last_session(); + break; + + case MI_session_save: + save_session(); + break; + + case MI_session_close: + close_session(); + break; + + case MI_session_export_json: + export_session(); + break; + + case MI_exit: + if (close_session()) { + exit(0); + } + break; + + case MI_time_ms: + set_time_units(PStatGraph::GBU_ms); + break; + + case MI_time_hz: + set_time_units(PStatGraph::GBU_hz); + break; + + default: + if (_monitor != nullptr) { + _monitor->handle_menu_command(menu_id); + } + break; + } +} diff --git a/pandatool/src/win-stats/winStatsServer.h b/pandatool/src/win-stats/winStatsServer.h new file mode 100644 index 00000000..1c1e1341 --- /dev/null +++ b/pandatool/src/win-stats/winStatsServer.h @@ -0,0 +1,82 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsServer.h + * @author drose + * @date 2003-12-02 + */ + +#ifndef WINSTATSSERVER_H +#define WINSTATSSERVER_H + +#include "pandatoolbase.h" +#include "programBase.h" +#include "pStatServer.h" +#include "winStatsMonitor.h" + +/** + * The class that owns the main loop, waiting for client connections. + */ +class WinStatsServer : public PStatServer, public ProgramBase { +public: + WinStatsServer(); + + virtual bool handle_args(Args &args) override; + + virtual PStatMonitor *make_monitor(const NetAddress &address) override; + virtual void lost_connection(PStatMonitor *monitor) override; + + bool new_session(); + bool open_session(); + bool open_last_session(); + bool save_session(); + bool export_session(); + bool close_session(); + + HWND get_window() const; + HMENU get_menu_bar() const; + HWND get_status_bar() const; + HFONT get_font() const; + int get_pixel_scale() const; + POINT get_client_origin() const; + + int get_time_units() const; + void set_time_units(int unit_mask); + +private: + void create_window(); + void setup_session_menu(); + void setup_options_menu(); + void create_status_bar(HINSTANCE application); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + void handle_menu_command(int menu_id); + + PT(WinStatsMonitor) _monitor; + + Filename _last_session; + + int _port = -1; + HWND _window = 0; + HMENU _menu_bar = 0; + HMENU _session_menu = 0; + HMENU _options_menu = 0; + HWND _status_bar; + POINT _client_origin; + int _time_units = 0; + int _pixel_scale; + + HFONT _font; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsStripChart.cxx b/pandatool/src/win-stats/winStatsStripChart.cxx new file mode 100644 index 00000000..f6f894ea --- /dev/null +++ b/pandatool/src/win-stats/winStatsStripChart.cxx @@ -0,0 +1,900 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsStripChart.cxx + * @author drose + * @date 2003-12-03 + */ + +#include "winStatsStripChart.h" +#include "winStatsMonitor.h" +#include "pStatCollectorDef.h" +#include "numeric_types.h" + +#include + +static const int default_strip_chart_width = 400; +static const int default_strip_chart_height = 100; + +bool WinStatsStripChart::_window_class_registered = false; +const char * const WinStatsStripChart::_window_class_name = "strip"; + +/** + * + */ +WinStatsStripChart:: +WinStatsStripChart(WinStatsMonitor *monitor, int thread_index, + int collector_index, bool show_level) : + PStatStripChart(monitor, + thread_index, collector_index, show_level, + monitor->get_pixel_scale() * default_strip_chart_width / 4, + monitor->get_pixel_scale() * default_strip_chart_height / 4), + WinStatsGraph(monitor) +{ + _brush_origin = 0; + + _left_margin = _pixel_scale * 24; + _right_margin = _pixel_scale * 12; + _top_margin = _pixel_scale * 6; + _bottom_margin = _pixel_scale * 2; + + if (show_level) { + // If it's a level-type graph, show the appropriate units. + if (_unit_name.empty()) { + set_guide_bar_units(GBU_named); + } else { + set_guide_bar_units(GBU_named | GBU_show_units); + } + + } else { + // If it's a time-type graph, show the msHz units. + set_guide_bar_units(get_guide_bar_units() | GBU_show_units); + } + + _smooth_check_box = 0; + + create_window(); + clear_region(); + + update(); +} + +/** + * + */ +WinStatsStripChart:: +~WinStatsStripChart() { +} + +/** + * Called whenever a new Collector definition is received from the client. + */ +void WinStatsStripChart:: +new_collector(int collector_index) { + WinStatsGraph::new_collector(collector_index); +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void WinStatsStripChart:: +new_data(int thread_index, int frame_number) { + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } + + if (!_pause) { + update(); + + std::string text = get_total_text(); + if (_net_value_text != text) { + _net_value_text = text; + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); + } + } +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void WinStatsStripChart:: +force_redraw() { + PStatStripChart::force_redraw(); +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void WinStatsStripChart:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatStripChart::changed_size(graph_xsize, graph_ysize); +} + +/** + * Called when the user selects a new time units from the monitor pulldown + * menu, this should adjust the units for the graph to the indicated mask if + * it is a time-based graph. + */ +void WinStatsStripChart:: +set_time_units(int unit_mask) { + int old_unit_mask = get_guide_bar_units(); + if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) { + unit_mask = unit_mask & (GBU_hz | GBU_ms); + unit_mask |= (old_unit_mask & GBU_show_units); + set_guide_bar_units(unit_mask); + + RECT rect; + GetClientRect(_window, &rect); + rect.left = _right_margin; + InvalidateRect(_window, &rect, TRUE); + + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); + } +} + +/** + * Called when the user selects a new scroll speed from the monitor pulldown + * menu, this should adjust the speed for the graph to the indicated value. + */ +void WinStatsStripChart:: +set_scroll_speed(double scroll_speed) { + // The speed factor indicates chart widths per minute. + if (scroll_speed != 0.0f) { + set_horizontal_scale(60.0f / scroll_speed); + } +} + +/** + * Called when the user single-clicks on a label. + */ +void WinStatsStripChart:: +on_click_label(int collector_index) { + if (collector_index < 0) { + // Clicking on whitespace in the graph is the same as clicking on the top + // label. + collector_index = get_collector_index(); + } + + if (collector_index == get_collector_index() && collector_index != 0) { + // Clicking on the top label means to go up to the parent level. + const PStatClientData *client_data = + WinStatsGraph::_monitor->get_client_data(); + if (client_data->has_collector(collector_index)) { + const PStatCollectorDef &def = + client_data->get_collector_def(collector_index); + if (def._parent_index == 0 && get_view().get_show_level()) { + // Unless the parent is "Frame", and we're not a time collector. + } else { + set_collector_index(def._parent_index); + } + } + + } else { + // Clicking on any other label means to focus on that. + set_collector_index(collector_index); + } +} + +/** + * Called when the user right-clicks on a label. + */ +void WinStatsStripChart:: +on_popup_label(int collector_index) { + POINT point; + if (collector_index >= 0 && GetCursorPos(&point)) { + _popup_index = collector_index; + + HMENU popup = CreatePopupMenu(); + + std::string label = get_label_tooltip(collector_index); + if (!label.empty()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 0, label.c_str()); + } + if (collector_index == 0 && get_collector_index() == 0) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 101, "Set as Focus"); + } else { + AppendMenu(popup, MF_STRING, 101, "Set as Focus"); + } + AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); + if (get_view().get_show_level()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 103, "Open Flame Graph"); + } else { + AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + } + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 104, "Change Color..."); + AppendMenu(popup, MF_STRING, 105, "Reset Color"); + TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _window, nullptr); + } +} + +/** + * Called when the mouse hovers over a label, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsStripChart:: +get_label_tooltip(int collector_index) const { + return PStatStripChart::get_label_tooltip(collector_index); +} + +/** + * Changes the collector represented by this strip chart. This may force a + * redraw. + */ +void WinStatsStripChart:: +set_collector_index(int collector_index) { + if (get_collector_index() != collector_index) { + PStatStripChart::set_collector_index(collector_index); + + if (is_title_unknown()) { + std::string window_title = get_title_text(); + if (!is_title_unknown()) { + SetWindowText(_window, window_title.c_str()); + } + } + + // Redraw the scale labels. + RECT rect; + GetClientRect(_window, &rect); + rect.left = _right_margin; + InvalidateRect(_window, &rect, TRUE); + } +} + +/** + * Changes the value the height of the vertical axis represents. This may + * force a redraw. + */ +void WinStatsStripChart:: +set_vertical_scale(double value_height) { + PStatStripChart::set_vertical_scale(value_height); + + RECT rect; + GetClientRect(_window, &rect); + rect.left = _right_margin; + InvalidateRect(_window, &rect, TRUE); +} + +/** + * Resets the list of labels. + */ +void WinStatsStripChart:: +update_labels() { + PStatStripChart::update_labels(); + + _label_stack.replace_labels(WinStatsGraph::_monitor, this, + _thread_index, _labels, false); + _labels_changed = false; +} + +/** + * Erases the chart area. + */ +void WinStatsStripChart:: +clear_region() { + RECT rect = { 0, 0, get_xsize(), get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +/** + * Should be overridden by the user class to copy a region of the chart from + * one part of the chart to another. This is used to implement scrolling. + */ +void WinStatsStripChart:: +copy_region(int start_x, int end_x, int dest_x) { + BitBlt(_bitmap_dc, dest_x, 0, + end_x - start_x, get_ysize(), + _bitmap_dc, start_x, 0, + SRCCOPY); + + // Also shift the brush origin over, so we still get proper dithering. + _brush_origin += (dest_x - start_x); + SetBrushOrgEx(_bitmap_dc, _brush_origin, 0, nullptr); + + RECT rect = { + dest_x, 0, dest_x + end_x - start_x, get_ysize() + }; + InvalidateRect(_graph_window, &rect, FALSE); +} + +/** + * Draws a single vertical slice of the strip chart, at the given pixel + * position, and corresponding to the indicated level data. + */ +void WinStatsStripChart:: +draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) { + // Start by clearing the band first. + RECT rect = { x, 0, x + w, get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); + + double overall_time = 0.0; + int y = get_ysize(); + + FrameData::const_iterator fi; + for (fi = fdata.begin(); fi != fdata.end(); ++fi) { + const ColorData &cd = (*fi); + overall_time += cd._net_value; + HBRUSH brush = get_collector_brush(cd._collector_index, cd._collector_index == _highlighted_index); + + if (overall_time > get_vertical_scale()) { + // Off the top. Go ahead and clamp it by hand, in case it's so far off + // the top we'd overflow the 16-bit pixel value. + rect.top = 0; + rect.bottom = y; + FillRect(_bitmap_dc, &rect, brush); + // And we can consider ourselves done now. + return; + } + + int top_y = height_to_pixel(overall_time); + rect.top = top_y; + rect.bottom = y; + FillRect(_bitmap_dc, &rect, brush); + y = top_y; + } +} + +/** + * Draws a single vertical slice of background color. + */ +void WinStatsStripChart:: +draw_empty(int x, int w) { + RECT rect = { x, 0, x + w, get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +/** + * Draws a single vertical slice of foreground color. + */ +void WinStatsStripChart:: +draw_cursor(int x) { + RECT rect = { x, 0, x + 1, get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); +} + +/** + * Should be overridden by the user class. This hook will be called after + * drawing a series of color bars in the strip chart; it gives the pixel range + * that was just redrawn. + */ +void WinStatsStripChart:: +end_draw(int from_x, int to_x) { + // Draw in the guide bars. + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; i++) { + draw_guide_bar(_bitmap_dc, from_x, to_x, get_guide_bar(i)); + } + + RECT rect = { + from_x, 0, to_x + 1, get_ysize() + }; + InvalidateRect(_graph_window, &rect, FALSE); +} + +/** + * Returns the current window dimensions. + */ +bool WinStatsStripChart:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsStripChart:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); + + // Set the state of the checkbox. + SendMessage(_smooth_check_box, BM_SETCHECK, get_average_mode() ? BST_CHECKED : BST_UNCHECKED, 0); +} + +/** + * + */ +LONG WinStatsStripChart:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + if (_potential_drag_mode == DM_new_guide_bar) { + set_drag_mode(DM_new_guide_bar); + SetCapture(_graph_window); + return 0; + } + break; + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case BN_CLICKED: + if ((HWND)lparam == _smooth_check_box) { + int result = SendMessage(_smooth_check_box, BM_GETCHECK, 0, 0); + set_average_mode(result == BST_CHECKED); + return 0; + } + break; + + case 101: + set_collector_index(_popup_index); + break; + + case 102: + WinStatsGraph::_monitor->open_strip_chart(get_thread_index(), _popup_index, + get_view().get_show_level()); + return 0; + + case 103: + WinStatsGraph::_monitor->open_flame_graph(get_thread_index(), _popup_index); + return 0; + + case 104: + WinStatsGraph::_monitor->choose_collector_color(_popup_index); + return 0; + + case 105: + WinStatsGraph::_monitor->reset_collector_color(_popup_index); + return 0; + } + break; + + default: + break; + } + + return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +LONG WinStatsStripChart:: +graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_scale); + int16_t y = HIWORD(lparam); + _drag_scale_start = pixel_to_height(y); + SetCapture(_graph_window); + return 0; + + } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) { + set_drag_mode(DM_guide_bar); + int16_t y = HIWORD(lparam); + _drag_start_y = y; + SetCapture(_graph_window); + return 0; + } + break; + + case WM_MOUSEMOVE: + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + + int collector_index = get_collector_under_pixel(x, y); + _label_stack.highlight_label(collector_index); + on_enter_label(collector_index); + + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + } + else { + // If the mouse is in some drag mode, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + } + + if (_drag_mode == DM_scale) { + int16_t y = HIWORD(lparam); + double ratio = 1.0 - ((double)y / (double)get_ysize()); + if (ratio > 0.0) { + double new_scale = _drag_scale_start / ratio; + if (!IS_NEARLY_EQUAL(get_vertical_scale(), new_scale)) { + // Disable smoothing while we do this expensive operation. + set_average_mode(false); + set_vertical_scale(_drag_scale_start / ratio); + } + } + return 0; + + } else if (_drag_mode == DM_new_guide_bar) { + // We haven't created the new guide bar yet; we won't until the mouse + // comes within the graph's region. + int16_t y = HIWORD(lparam); + if (y >= 0 && y < get_ysize()) { + set_drag_mode(DM_guide_bar); + _drag_guide_bar = add_user_guide_bar(pixel_to_height(y)); + return 0; + } + + } else if (_drag_mode == DM_guide_bar) { + int16_t y = HIWORD(lparam); + move_user_guide_bar(_drag_guide_bar, pixel_to_height(y)); + return 0; + } + break; + + case WM_MOUSELEAVE: + // When the mouse leaves the graph, stop highlighting. + _label_stack.highlight_label(-1); + on_leave_label(_highlighted_index); + break; + + case WM_LBUTTONUP: + if (_drag_mode == DM_scale) { + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + + } else if (_drag_mode == DM_guide_bar) { + int16_t y = HIWORD(lparam); + if (y < 0 || y >= get_ysize()) { + remove_user_guide_bar(_drag_guide_bar); + } else { + move_user_guide_bar(_drag_guide_bar, pixel_to_height(y)); + } + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + } + break; + + case WM_LBUTTONDBLCLK: + { + // Double-clicking on a color bar in the graph is the same as double- + // clicking on the corresponding label. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + on_click_label(get_collector_under_pixel(x, y)); + return 0; + } + break; + + case WM_CONTEXTMENU: + { + POINT point; + if (GetCursorPos(&point) && ScreenToClient(_graph_window, &point)) { + int collector_index = get_collector_under_pixel(point.x, point.y); + if (collector_index >= 0) { + on_popup_label(collector_index); + } + } + return 0; + } + break; + + default: + break; + } + + return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsStripChart:: +additional_window_paint(HDC hdc) { + // Draw in the labels for the guide bars. + SelectObject(hdc, WinStatsGraph::_monitor->get_font()); + SetTextAlign(hdc, TA_LEFT | TA_TOP); + SetBkMode(hdc, TRANSPARENT); + + RECT rect; + GetClientRect(_window, &rect); + int x = rect.right - _right_margin + _pixel_scale; + int last_y = -100; + + int i; + int num_guide_bars = get_num_guide_bars(); + for (i = 0; i < num_guide_bars; i++) { + last_y = draw_guide_label(hdc, x, get_guide_bar(i), last_y); + } + + GuideBar top_value = make_guide_bar(get_vertical_scale()); + draw_guide_label(hdc, x, top_value, last_y); + + last_y = -100; + int num_user_guide_bars = get_num_user_guide_bars(); + for (i = 0; i < num_user_guide_bars; i++) { + last_y = draw_guide_label(hdc, x, get_user_guide_bar(i), last_y); + } + + // Now draw the "net value" label at the top. + SetTextAlign(hdc, TA_RIGHT | TA_BOTTOM); + SetTextColor(hdc, RGB(0, 0, 0)); + TextOut(hdc, rect.right - _right_margin - _pixel_scale, _top_margin - _pixel_scale / 2, + _net_value_text.data(), _net_value_text.length()); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsStripChart:: +additional_graph_window_paint(HDC hdc) { + int num_user_guide_bars = get_num_user_guide_bars(); + for (int i = 0; i < num_user_guide_bars; i++) { + draw_guide_bar(hdc, 0, get_xsize(), get_user_guide_bar(i)); + } +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsStripChart:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + if (_highlighted_index != -1) { + return get_label_tooltip(_highlighted_index); + } + return std::string(); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +WinStatsGraph::DragMode WinStatsStripChart:: +consider_drag_start(int mouse_x, int mouse_y, int width, int height) { + if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) { + if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) { + // See if the mouse is over a user-defined guide bar. + int y = mouse_y - _graph_top; + double from_height = pixel_to_height(y + 2); + double to_height = pixel_to_height(y - 2); + _drag_guide_bar = find_user_guide_bar(from_height, to_height); + if (_drag_guide_bar >= 0) { + return DM_guide_bar; + } + + } else { + // The mouse is above or below the graph; maybe create a new guide bar. + return DM_new_guide_bar; + } + } + + return WinStatsGraph::consider_drag_start(mouse_x, mouse_y, width, height); +} + +/** + * This should be called whenever the drag mode needs to change state. It + * provides hooks for a derived class to do something special. + */ +void WinStatsStripChart:: +set_drag_mode(WinStatsGraph::DragMode drag_mode) { + WinStatsGraph::set_drag_mode(drag_mode); + + if (_drag_mode == DM_none) { + // Restore smoothing according to the current setting of the check box. + int result = SendMessage(_smooth_check_box, BM_GETCHECK, 0, 0); + set_average_mode(result == BST_CHECKED); + } +} + +/** + * Repositions the graph child window within the parent window according to + * the _margin variables. + */ +void WinStatsStripChart:: +move_graph_window(int graph_left, int graph_top, int graph_xsize, int graph_ysize) { + WinStatsGraph::move_graph_window(graph_left, graph_top, graph_xsize, graph_ysize); + if (_smooth_check_box != 0) { + SIZE size; + SendMessage(_smooth_check_box, BCM_GETIDEALSIZE, 0, (LPARAM)&size); + + SetWindowPos(_smooth_check_box, 0, + _left_margin, _top_margin - size.cy - _pixel_scale / 2, + size.cx, size.cy, + SWP_NOZORDER | SWP_SHOWWINDOW); + InvalidateRect(_smooth_check_box, nullptr, TRUE); + } +} + +/** + * Draws the line for the indicated guide bar on the graph. + */ +void WinStatsStripChart:: +draw_guide_bar(HDC hdc, int from_x, int to_x, + const PStatGraph::GuideBar &bar) { + int y = height_to_pixel(bar._height); + + if (y > 0) { + // Only draw it if it's not too close to the top. + switch (bar._style) { + case GBS_target: + SelectObject(hdc, _light_pen); + break; + + case GBS_user: + SelectObject(hdc, _user_guide_bar_pen); + break; + + case GBS_normal: + SelectObject(hdc, _dark_pen); + break; + } + MoveToEx(hdc, from_x, y, nullptr); + LineTo(hdc, to_x + 1, y); + } +} + +/** + * Draws the text for the indicated guide bar label to the right of the graph, + * unless it would overlap with the indicated last label, whose top pixel + * value is given. Returns the top pixel value of the new label. + */ +int WinStatsStripChart:: +draw_guide_label(HDC hdc, int x, const PStatGraph::GuideBar &bar, int last_y) { + switch (bar._style) { + case GBS_target: + SetTextColor(hdc, _light_color); + break; + + case GBS_user: + SetTextColor(hdc, _user_guide_bar_color); + break; + + case GBS_normal: + SetTextColor(hdc, _dark_color); + break; + } + + int y = height_to_pixel(bar._height); + const std::string &label = bar._label; + SIZE size; + GetTextExtentPoint32(hdc, label.data(), label.length(), &size); + + if (bar._style != GBS_user) { + double from_height = pixel_to_height(y + size.cy); + double to_height = pixel_to_height(y - size.cy); + if (find_user_guide_bar(from_height, to_height) >= 0) { + // Omit the label: there's a user-defined guide bar in the same space. + return last_y; + } + } + + int this_y = _graph_top + y - size.cy / 2; + if (y >= 0 && y < get_ysize() && + (last_y < this_y || last_y > this_y + size.cy)) { + TextOut(hdc, x, this_y, + label.data(), label.length()); + last_y = this_y; + } + + return last_y; +} + + +/** + * Creates the window for this strip chart. + */ +void WinStatsStripChart:: +create_window() { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + std::string window_title = get_title_text(); + POINT window_pos = WinStatsGraph::_monitor->get_new_window_pos(); + + RECT win_rect = { + 0, 0, + _left_margin + get_xsize() + _right_margin, + _top_margin + get_ysize() + _bottom_margin + }; + + // compute window size based on desired client area size + AdjustWindowRect(&win_rect, graph_window_style, FALSE); + + _window = + CreateWindowEx(WS_EX_DLGMODALFRAME, _window_class_name, + window_title.c_str(), graph_window_style, + window_pos.x, window_pos.y, + win_rect.right - win_rect.left, + win_rect.bottom - win_rect.top, + WinStatsGraph::_monitor->get_window(), + nullptr, application, 0); + if (!_window) { + nout << "Could not create StripChart window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + setup_label_stack(); + + _smooth_check_box = + CreateWindow(WC_BUTTON, "Smooth", WS_CHILD | BS_AUTOCHECKBOX, + 0, 0, 0, 0, + _window, nullptr, application, 0); + SendMessage(_smooth_check_box, WM_SETFONT, + (WPARAM)WinStatsGraph::_monitor->get_font(), TRUE); + + if (get_average_mode()) { + SendMessage(_smooth_check_box, BM_SETCHECK, BST_CHECKED, 0); + } + + // Ensure that the window is on top of the stack. + SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); +} + +/** + * Registers the window class for the stripChart window, if it has not already + * been registered. + */ +void WinStatsStripChart:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsStripChart *); + + if (!RegisterClass(&wc)) { + nout << "Could not register StripChart window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsStripChart:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsStripChart *self = (WinStatsStripChart *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} diff --git a/pandatool/src/win-stats/winStatsStripChart.h b/pandatool/src/win-stats/winStatsStripChart.h new file mode 100644 index 00000000..ac8728a1 --- /dev/null +++ b/pandatool/src/win-stats/winStatsStripChart.h @@ -0,0 +1,98 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsStripChart.h + * @author drose + * @date 2003-12-03 + */ + +#ifndef WINSTATSSTRIPCHART_H +#define WINSTATSSTRIPCHART_H + +#include "pandatoolbase.h" + +#include "winStatsGraph.h" +#include "pStatStripChart.h" +#include "pointerTo.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsMonitor; + +/** + * A window that draws a strip chart, given a view. + */ +class WinStatsStripChart : public PStatStripChart, public WinStatsGraph { +public: + WinStatsStripChart(WinStatsMonitor *monitor, + int thread_index, int collector_index, bool show_level); + virtual ~WinStatsStripChart(); + + virtual void new_collector(int collector_index); + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + + virtual void set_time_units(int unit_mask); + virtual void set_scroll_speed(double scroll_speed); + virtual void on_click_label(int collector_index); + virtual void on_popup_label(int collector_index); + virtual std::string get_label_tooltip(int collector_index) const; + + void set_collector_index(int collector_index); + void set_vertical_scale(double value_height); + +protected: + virtual void update_labels(); + + virtual void clear_region(); + virtual void copy_region(int start_x, int end_x, int dest_x); + virtual void draw_slice(int x, int w, + const PStatStripChart::FrameData &fdata); + virtual void draw_empty(int x, int w); + virtual void draw_cursor(int x); + virtual void end_draw(int from_x, int to_x); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual void additional_window_paint(HDC hdc); + virtual void additional_graph_window_paint(HDC hdc); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int mouse_x, int mouse_y, + int width, int height); + virtual void set_drag_mode(DragMode drag_mode); + virtual void move_graph_window(int graph_left, int graph_top, + int graph_xsize, int graph_ysize); + +private: + void draw_guide_bar(HDC hdc, int from_x, int to_x, const GuideBar &bar); + int draw_guide_label(HDC hdc, int x, const GuideBar &bar, int last_y); + void create_window(); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + int _brush_origin; + std::string _net_value_text; + + HWND _smooth_check_box; + int _popup_index = -1; + + static bool _window_class_registered; + static const char * const _window_class_name; +}; + +#endif diff --git a/pandatool/src/win-stats/winStatsTimeline.cxx b/pandatool/src/win-stats/winStatsTimeline.cxx new file mode 100644 index 00000000..a9c3f668 --- /dev/null +++ b/pandatool/src/win-stats/winStatsTimeline.cxx @@ -0,0 +1,805 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsTimeline.cxx + * @author rdb + * @date 2022-02-11 + */ + +#include "winStatsTimeline.h" +#include "winStatsMonitor.h" +#include "numeric_types.h" + +static const int default_timeline_width = 1000; +static const int default_timeline_height = 500; + +bool WinStatsTimeline::_window_class_registered = false; +const char * const WinStatsTimeline::_window_class_name = "timeline"; + +/** + * + */ +WinStatsTimeline:: +WinStatsTimeline(WinStatsMonitor *monitor) : + PStatTimeline(monitor, + monitor->get_pixel_scale() * default_timeline_width / 4, + monitor->get_pixel_scale() * default_timeline_height / 4), + WinStatsGraph(monitor) +{ + _left_margin = _pixel_scale * 24; + _right_margin = _pixel_scale * 2; + _top_margin = _pixel_scale * 5; + _bottom_margin = _pixel_scale * 2; + + normal_guide_bars(); + + create_window(); + clear_region(); + + _grid_brush = CreateSolidBrush(RGB(0xdd, 0xdd, 0xdd)); +} + +/** + * + */ +WinStatsTimeline:: +~WinStatsTimeline() { +} + +/** + * Called as each frame's data is made available. There is no guarantee the + * frames will arrive in order, or that all of them will arrive at all. The + * monitor should be prepared to accept frames received out-of-order or + * missing. + */ +void WinStatsTimeline:: +new_data(int thread_index, int frame_number) { + PStatTimeline::new_data(thread_index, frame_number); +} + +/** + * Called when it is necessary to redraw the entire graph. + */ +void WinStatsTimeline:: +force_redraw() { + PStatTimeline::force_redraw(); +} + +/** + * Called when the user has resized the window, forcing a resize of the graph. + */ +void WinStatsTimeline:: +changed_graph_size(int graph_xsize, int graph_ysize) { + PStatTimeline::changed_size(graph_xsize, graph_ysize); +} + +/** + * Erases the chart area. + */ +void WinStatsTimeline:: +clear_region() { + RECT rect = { 0, 0, get_xsize(), get_ysize() }; + FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); +} + +/** + * Erases the chart area in preparation for drawing a bunch of bars. + */ +void WinStatsTimeline:: +begin_draw() { + SelectObject(_bitmap_dc, WinStatsGraph::_monitor->get_font()); + SelectObject(_bitmap_dc, GetStockObject(NULL_PEN)); + SetBkMode(_bitmap_dc, TRANSPARENT); + SetTextAlign(_bitmap_dc, TA_LEFT | TA_TOP | TA_NOUPDATECP); +} + +/** + * Draws a horizontal separator. + */ +void WinStatsTimeline:: +draw_separator(int row) { + int y = (row_to_pixel(row) + row_to_pixel(row + 1)) / 2; + RECT rect = {0, y, get_xsize(), y + _pixel_scale / 3}; + FillRect(_bitmap_dc, &rect, _grid_brush); +} + +/** + * Draws a vertical guide bar. If the row is -1, draws it in all rows. + */ +void WinStatsTimeline:: +draw_guide_bar(int x, GuideBarStyle style) { + int x1 = x - _pixel_scale / 6; + int x2 = x1 + _pixel_scale / 3; + if (style == GBS_frame) { + ++x2; + } + RECT rect = {x1, 0, x2, get_ysize()}; + FillRect(_bitmap_dc, &rect, _grid_brush); +} + +/** + * Draws a single bar in the chart for the indicated row, in the color for the + * given collector, for the indicated horizontal pixel range. + */ +void WinStatsTimeline:: +draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name) { + + int top = row_to_pixel(row); + int bottom = row_to_pixel(row + 1); + + bool is_highlighted = row == _highlighted_row && _highlighted_x >= from_x && _highlighted_x < to_x; + HBRUSH brush = get_collector_brush(collector_index, is_highlighted); + + if (to_x < from_x + 2) { + // It's just a tiny sliver. This is a more reliable way to draw it. + RECT rect = {from_x, top + 1, from_x + 1, bottom - 1}; + FillRect(_bitmap_dc, &rect, brush); + + //if (to_x <= from_x + 2) { + // // Draw an arrow pointing to it, if it's so small. + // POINT vertices[] = {{to_x, bottom}, {to_x - _pixel_scale, bottom + _pixel_scale * 2}, {to_x + _pixel_scale, bottom + _pixel_scale * 2}}; + // Polygon(_bitmap_dc, vertices, 3); + //} + } + else { + SelectObject(_bitmap_dc, brush); + RoundRect(_bitmap_dc, + std::max(from_x, -_pixel_scale - 1), + top, + std::min(std::max(to_x, from_x + 1), get_xsize() + _pixel_scale), + bottom, + _pixel_scale, + _pixel_scale); + + if ((to_x - from_x) >= _pixel_scale * 4) { + // Only bother drawing the text if we've got some space to draw on. + // Choose a suitable foreground color. + SetTextColor(_bitmap_dc, get_collector_text_color(collector_index, is_highlighted)); + + // Make sure that the text doesn't run off the chart. + SIZE size; + GetTextExtentPoint32(_bitmap_dc, collector_name.data(), collector_name.size(), &size); + int center = (from_x + to_x) / 2; + int left = std::max(from_x, 0) + _pixel_scale / 2; + int right = std::min(to_x, get_xsize()) - _pixel_scale / 2; + + if (size.cx >= right - left) { + if (right - left < _pixel_scale * 6) { + // It's a really tiny space. Draw a single letter. + RECT rect = {left, top, right, bottom}; + DrawText(_bitmap_dc, collector_name.data(), 1, + &rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER); + } else { + // It's going to be tricky to fit it, let Windows figure it out via + // the more expensive DrawText call. + RECT rect = {left, top, right, bottom}; + DrawText(_bitmap_dc, collector_name.data(), collector_name.size(), + &rect, DT_CENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER); + } + } + else { + int text_top = top + (bottom - top - size.cy) / 2; + if (center - size.cx / 2 < 0) { + // Put it against the left-most edge. + TextOut(_bitmap_dc, _pixel_scale, text_top, + collector_name.data(), collector_name.length()); + } + else if (center + size.cx / 2 >= get_xsize()) { + // Put it against the right-most edge. + TextOut(_bitmap_dc, get_xsize() - _pixel_scale - size.cx, text_top, + collector_name.data(), collector_name.length()); + } + else { + // It fits just fine, center it. + TextOut(_bitmap_dc, center - size.cx / 2, text_top, + collector_name.data(), collector_name.length()); + } + } + } + } +} + +/** + * Called after all the bars have been drawn, this triggers a refresh event to + * draw it to the window. + */ +void WinStatsTimeline:: +end_draw() { + InvalidateRect(_graph_window, nullptr, FALSE); + + if (_threads_changed) { + RECT rect; + GetClientRect(_window, &rect); + rect.top = _top_margin; + rect.right = _left_margin; + InvalidateRect(_window, &rect, TRUE); + _threads_changed = false; + } + + if (_guide_bars_changed) { + RECT rect; + GetClientRect(_window, &rect); + rect.bottom = _top_margin; + InvalidateRect(_window, &rect, TRUE); + _guide_bars_changed = false; + } +} + +/** + * Called at the end of the draw cycle. + */ +void WinStatsTimeline:: +idle() { +} + +/** + * Overridden by a derived class to implement an animation. If it returns + * false, the animation timer is stopped. + */ +bool WinStatsTimeline:: +animate(double time, double dt) { + return PStatTimeline::animate(time, dt); +} + +/** + * Returns the current window dimensions. + */ +bool WinStatsTimeline:: +get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const { + WinStatsGraph::get_window_state(x, y, width, height, maximized, minimized); + return true; +} + +/** + * Called to restore the graph window to its previous dimensions. + */ +void WinStatsTimeline:: +set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized) { + WinStatsGraph::set_window_state(x, y, width, height, maximized, minimized); +} + +/** + * + */ +LONG WinStatsTimeline:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_MOUSELEAVE: + SetFocus(nullptr); + break; + + default: + break; + } + + return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam); +} + +/** + * + */ +LONG WinStatsTimeline:: +graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch (msg) { + case WM_LBUTTONDOWN: + if (_potential_drag_mode == DM_none) { + set_drag_mode(DM_pan); + int16_t x = LOWORD(lparam); + _drag_start_x = x; + _scroll_speed = 0.0; + _zoom_center = pixel_to_timestamp(x); + SetCapture(_graph_window); + return 0; + } + break; + + case WM_MOUSEMOVE: + // Make sure we can accept keyboard events, except if we're inactive. + if (GetActiveWindow() == _window) { + SetFocus(hwnd); + } + + if (_drag_mode == DM_none && _potential_drag_mode == DM_none) { + // When the mouse is over a color bar, highlight it. + int x = LOWORD(lparam); + int y = HIWORD(lparam); + double time = pixel_to_timestamp(x); + + int row = pixel_to_row(y); + + if (row != _highlighted_row) { + clear_graph_tooltip(); + } + else if (_highlighted_row >= 0) { + // Is the mouse on the same bar? If not, clear the tooltip. + ColorBar bar; + if (find_bar(row, x, bar)) { + double prev_time = pixel_to_timestamp(_highlighted_x); + if (prev_time < bar._start || prev_time > bar._end) { + clear_graph_tooltip(); + } + } else { + clear_graph_tooltip(); + } + } + + std::swap(_highlighted_x, x); + std::swap(_highlighted_row, row); + + if (row >= 0) { + PStatTimeline::force_redraw(row, x, x); + } + PStatTimeline::force_redraw(_highlighted_row, _highlighted_x, _highlighted_x); + + if ((_keys_held & (F_w | F_s)) != 0) { + // Update the zoom center if we move the mouse while zooming with the + // keyboard. + _zoom_center = time; + } + + // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph + // window. + TRACKMOUSEEVENT tme = { + sizeof(TRACKMOUSEEVENT), + TME_LEAVE, + _graph_window, + 0 + }; + TrackMouseEvent(&tme); + } + else { + // If the mouse is in some drag mode, stop highlighting. + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + clear_graph_tooltip(); + } + } + + if (_drag_mode == DM_pan) { + int16_t x = LOWORD(lparam); + int delta = _drag_start_x - x; + set_horizontal_scroll(get_horizontal_scroll() + pixel_to_height(delta)); + _drag_start_x = x; + return 0; + } + break; + + case WM_MOUSELEAVE: + // When the mouse leaves the graph, stop highlighting. + if (_highlighted_row != -1) { + int row = _highlighted_row; + _highlighted_row = -1; + PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x); + clear_graph_tooltip(); + } + SetFocus(nullptr); + break; + + case WM_LBUTTONUP: + if (_drag_mode == DM_pan) { + set_drag_mode(DM_none); + ReleaseCapture(); + return 0; + } + break; + + case WM_LBUTTONDBLCLK: + { + // Double-clicking on a color bar in the graph will zoom the graph into + // that collector. + int16_t x = LOWORD(lparam); + int16_t y = HIWORD(lparam); + int row = pixel_to_row(y); + ColorBar bar; + if (find_bar(row, x, bar)) { + double width = bar._end - bar._start; + zoom_to(width * 1.5, pixel_to_timestamp(x)); + scroll_to(bar._start - width / 4.0); + } else { + // Double-clicking the white area zooms out. + _zoom_speed -= 100.0; + } + start_animation(); + return 0; + } + break; + + case WM_CONTEXTMENU: + { + // Right-clicking a color bar brings up a context menu. + POINT point; + if (GetCursorPos(&point)) { + POINT graph_point = point; + if (ScreenToClient(_graph_window, &graph_point)) { + int row = pixel_to_row(graph_point.y); + ColorBar bar; + if (find_bar(row, graph_point.x, bar)) { + _popup_bar = bar; + + HMENU popup = CreatePopupMenu(); + + std::string label = get_bar_tooltip(row, graph_point.x); + if (!label.empty()) { + AppendMenu(popup, MF_STRING | MF_DISABLED, 0, label.c_str()); + } + AppendMenu(popup, MF_STRING, 101, "Zoom To"); + AppendMenu(popup, MF_STRING, 102, "Open Strip Chart"); + AppendMenu(popup, MF_STRING, 103, "Open Flame Graph"); + AppendMenu(popup, MF_STRING, 104, "Open Piano Roll"); + AppendMenu(popup, MF_STRING | MF_SEPARATOR, 0, nullptr); + AppendMenu(popup, MF_STRING, 105, "Change Color..."); + AppendMenu(popup, MF_STRING, 106, "Reset Color"); + TrackPopupMenu(popup, TPM_LEFTBUTTON, point.x, point.y, 0, _graph_window, nullptr); + } + } + } + return 0; + } + break; + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case 101: + { + double width = _popup_bar._end - _popup_bar._start; + zoom_to(width * 1.5, (_popup_bar._end + _popup_bar._start) / 2.0); + scroll_to(_popup_bar._start - width / 4.0); + start_animation(); + } + return 0; + + case 102: + WinStatsGraph::_monitor->open_strip_chart(_popup_bar._thread_index, _popup_bar._collector_index, false); + return 0; + + case 103: + WinStatsGraph::_monitor->open_flame_graph(_popup_bar._thread_index, _popup_bar._collector_index, _popup_bar._frame_number); + return 0; + + case 104: + WinStatsGraph::_monitor->open_piano_roll(_popup_bar._thread_index); + return 0; + + case 105: + WinStatsGraph::_monitor->choose_collector_color(_popup_bar._collector_index); + return 0; + + case 106: + WinStatsGraph::_monitor->reset_collector_color(_popup_bar._collector_index); + return 0; + } + break; + + case WM_MOUSEWHEEL: + { + if (GET_KEYSTATE_WPARAM(wparam) & MK_CONTROL) { + // Zoom in/out around the cursor position. + POINT point; + if (GetCursorPos(&point) && ScreenToClient(_graph_window, &point)) { + int delta = GET_WHEEL_DELTA_WPARAM(wparam); + zoom_by(delta / 120.0, pixel_to_timestamp(point.x)); + start_animation(); + } + } else { + int delta = GET_WHEEL_DELTA_WPARAM(wparam); + delta = (delta * _pixel_scale * 5) / 120; + int new_scroll = _scroll - delta; + if (_threads.empty()) { + new_scroll = 0; + } else { + new_scroll = (std::min)(new_scroll, get_num_rows() * _pixel_scale * 5 + _pixel_scale * 2 - get_ysize()); + new_scroll = (std::max)(new_scroll, 0); + } + delta = new_scroll - _scroll; + if (delta != 0) { + _scroll = new_scroll; + _threads_changed = true; + PStatTimeline::force_redraw(); + } + } + return 0; + } + break; + + case WM_MOUSEHWHEEL: + { + int delta = GET_WHEEL_DELTA_WPARAM(wparam); + _scroll_speed += delta / 12.0; + start_animation(); + return 0; + } + break; + + case WM_KEYDOWN: + case WM_KEYUP: + { + int flag = 0; + int vsc = (lparam & 0xff0000) >> 16; + if ((lparam & 0x1000000) == 0) { + // Accept WASD based on their position rather than their mapping + switch (vsc) { + case 17: + flag = F_w; + break; + case 30: + flag = F_a; + break; + case 31: + flag = F_s; + break; + case 32: + flag = F_d; + break; + } + } + if (flag == 0) { + switch (wparam) { + case VK_LEFT: + flag = F_left; + break; + case VK_RIGHT: + flag = F_right; + break; + case 'W': + flag = F_w; + break; + case 'A': + flag = F_a; + break; + case 'S': + flag = F_s; + break; + case 'D': + flag = F_d; + break; + } + } + if (flag != 0) { + if (msg == WM_KEYDOWN) { + if (flag & (F_w | F_s)) { + POINT point; + if (GetCursorPos(&point) && ScreenToClient(_graph_window, &point)) { + _zoom_center = pixel_to_timestamp(point.x); + } else { + _zoom_center = get_horizontal_scroll() + get_horizontal_scale() / 2.0; + } + } + if (_keys_held == 0) { + start_animation(); + } + _keys_held |= flag; + } + else if (_keys_held != 0) { + _keys_held &= ~flag; + } + } + } + break; + + case WM_KILLFOCUS: + _keys_held = 0; + break; + + default: + break; + } + + return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam); +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsTimeline:: +additional_window_paint(HDC hdc) { + // Draw in the labels for the guide bars. + SelectObject(hdc, WinStatsGraph::_monitor->get_font()); + SetTextAlign(hdc, TA_LEFT | TA_BOTTOM); + SetBkMode(hdc, TRANSPARENT); + + int y = _top_margin - 2; + + int num_guide_bars = get_num_guide_bars(); + for (int i = 0; i < num_guide_bars; ++i) { + draw_guide_label(hdc, y, get_guide_bar(i)); + } + + SetTextColor(hdc, _dark_color); + SetTextAlign(hdc, TA_LEFT | TA_TOP | TA_NOUPDATECP); + + for (const ThreadRow &thread_row : _threads) { + if (thread_row._visible) { + draw_thread_label(hdc, thread_row); + } + } +} + +/** + * This is called during the servicing of WM_PAINT; it gives a derived class + * opportunity to do some further painting into the window (the outer window, + * not the graph window). + */ +void WinStatsTimeline:: +additional_graph_window_paint(HDC hdc) { +} + +/** + * Called when the mouse hovers over the graph, and should return the text that + * should appear on the tooltip. + */ +std::string WinStatsTimeline:: +get_graph_tooltip(int mouse_x, int mouse_y) const { + return PStatTimeline::get_bar_tooltip(pixel_to_row(mouse_y), mouse_x); +} + +/** + * Based on the mouse position within the window's client area, look for + * draggable things the mouse might be hovering over and return the + * apprioprate DragMode enum or DM_none if nothing is indicated. + */ +WinStatsGraph::DragMode WinStatsTimeline:: +consider_drag_start(int mouse_x, int mouse_y, int width, int height) { + DragMode mode = WinStatsGraph::consider_drag_start(mouse_x, mouse_y, width, height); + if (mode == DM_right_margin) { + mode = DM_none; + } + return mode; +} + +/** + * Draws the text for the indicated guide bar label at the top of the graph. + */ +void WinStatsTimeline:: +draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar) { + const std::string &label = bar._label; + if (label.empty()) { + return; + } + + bool center = true; + switch (bar._style) { + case GBS_target: + SetTextColor(hdc, _light_color); + break; + + case GBS_user: + SetTextColor(hdc, _user_guide_bar_color); + break; + + case GBS_normal: + SetTextColor(hdc, _light_color); + break; + + case GBS_frame: + SetTextColor(hdc, _dark_color); + center = false; + break; + } + + int x = timestamp_to_pixel(bar._height); + SIZE size; + GetTextExtentPoint32(hdc, label.data(), label.length(), &size); + + int this_x = _graph_left + x; + if (center) { + this_x -= size.cx / 2; + } + if (x >= 0 && x < get_xsize()) { + TextOut(hdc, this_x, y, + label.data(), label.length()); + } +} + +/** + * Draws the text for the indicated thread on the side of the graph. + */ +void WinStatsTimeline:: +draw_thread_label(HDC hdc, const ThreadRow &thread_row) { + int top = row_to_pixel(thread_row._row_offset + 1); + int bottom = row_to_pixel(thread_row._row_offset + 2); + + RECT rect = {_pixel_scale * 2, top, _left_margin - _pixel_scale * 2, bottom}; + DrawText(hdc, thread_row._label.data(), thread_row._label.size(), + &rect, DT_RIGHT | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER); +} + +/** + * Creates the window for this strip chart. + */ +void WinStatsTimeline:: +create_window() { + if (_window) { + return; + } + + HINSTANCE application = GetModuleHandle(nullptr); + register_window_class(application); + + POINT window_pos = WinStatsGraph::_monitor->get_new_window_pos(); + + RECT win_rect = { + 0, 0, + _left_margin + get_xsize() + _right_margin, + _top_margin + get_ysize() + _bottom_margin + }; + + // compute window size based on desired client area size + AdjustWindowRect(&win_rect, graph_window_style, FALSE); + + _window = + CreateWindowEx(WS_EX_DLGMODALFRAME, _window_class_name, + "Timeline", graph_window_style, + window_pos.x, window_pos.y, + win_rect.right - win_rect.left, + win_rect.bottom - win_rect.top, + WinStatsGraph::_monitor->get_window(), nullptr, application, 0); + if (!_window) { + nout << "Could not create timeline window!\n"; + exit(1); + } + + SetWindowLongPtr(_window, 0, (LONG_PTR)this); + + // Ensure that the window is on top of the stack. + SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + + SetFocus(_window); +} + +/** + * Registers the window class for the Timeline window, if it has not already + * been registered. + */ +void WinStatsTimeline:: +register_window_class(HINSTANCE application) { + if (_window_class_registered) { + return; + } + + WNDCLASS wc; + + ZeroMemory(&wc, sizeof(WNDCLASS)); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)static_window_proc; + wc.hInstance = application; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.lpszMenuName = nullptr; + wc.lpszClassName = _window_class_name; + + // Reserve space to associate the this pointer with the window. + wc.cbWndExtra = sizeof(WinStatsTimeline *); + + if (!RegisterClass(&wc)) { + nout << "Could not register Timeline window class!\n"; + exit(1); + } + + _window_class_registered = true; +} + +/** + * + */ +LONG WINAPI WinStatsTimeline:: +static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinStatsTimeline *self = (WinStatsTimeline *)GetWindowLongPtr(hwnd, 0); + if (self != nullptr && self->_window == hwnd) { + return self->window_proc(hwnd, msg, wparam, lparam); + } else { + return DefWindowProc(hwnd, msg, wparam, lparam); + } +} diff --git a/pandatool/src/win-stats/winStatsTimeline.h b/pandatool/src/win-stats/winStatsTimeline.h new file mode 100644 index 00000000..8c66b2b1 --- /dev/null +++ b/pandatool/src/win-stats/winStatsTimeline.h @@ -0,0 +1,95 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winStatsTimeline.h + * @author rdb + * @date 2022-02-11 + */ + +#ifndef WINSTATSTIMELINE_H +#define WINSTATSTIMELINE_H + +#include "pandatoolbase.h" + +#include "winStatsGraph.h" +#include "pStatTimeline.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +class WinStatsMonitor; + +/** + * A window that draws all of the start/stop event pairs on each thread on a + * horizontal scrolling timeline, with concurrent start/stop pairs stacked + * underneath each other. + */ +class WinStatsTimeline : public PStatTimeline, public WinStatsGraph { +public: + WinStatsTimeline(WinStatsMonitor *monitor); + virtual ~WinStatsTimeline(); + + virtual void new_data(int thread_index, int frame_number); + virtual void force_redraw(); + virtual void changed_graph_size(int graph_xsize, int graph_ysize); + +protected: + virtual void clear_region(); + virtual void begin_draw(); + virtual void draw_separator(int row); + virtual void draw_guide_bar(int x, GuideBarStyle style); + virtual void draw_bar(int row, int from_x, int to_x, int collector_index, + const std::string &collector_name); + virtual void end_draw(); + virtual void idle(); + + virtual bool animate(double time, double dt); + + virtual bool get_window_state(int &x, int &y, int &width, int &height, + bool &maximized, bool &minimized) const; + virtual void set_window_state(int x, int y, int width, int height, + bool maximized, bool minimized); + + LONG window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual LONG graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + virtual void additional_window_paint(HDC hdc); + virtual void additional_graph_window_paint(HDC hdc); + virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const; + virtual DragMode consider_drag_start(int mouse_x, int mouse_y, + int width, int height); + +private: + void draw_guide_label(HDC hdc, int y, const GuideBar &bar); + void draw_thread_label(HDC hdc, const ThreadRow &thread_row); + + void create_window(); + static void register_window_class(HINSTANCE application); + + static LONG WINAPI static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + int row_to_pixel(int y) const { + return y * _pixel_scale * 5 + _pixel_scale - _scroll; + } + int pixel_to_row(int y) const { + return (y + _scroll - _pixel_scale) / (_pixel_scale * 5); + } + + static bool _window_class_registered; + static const char * const _window_class_name; + + HBRUSH _grid_brush; + + int _highlighted_row = -1; + int _highlighted_x = 0; + int _scroll = 0; + ColorBar _popup_bar; +}; + +#endif diff --git a/pandatool/src/win-stats/winstats_composite1.cxx b/pandatool/src/win-stats/winstats_composite1.cxx new file mode 100644 index 00000000..ef632802 --- /dev/null +++ b/pandatool/src/win-stats/winstats_composite1.cxx @@ -0,0 +1,11 @@ +#include "winStats.cxx" +#include "winStatsChartMenu.cxx" +#include "winStatsFlameGraph.cxx" +#include "winStatsGraph.cxx" +#include "winStatsLabel.cxx" +#include "winStatsLabelStack.cxx" +#include "winStatsMonitor.cxx" +#include "winStatsPianoRoll.cxx" +#include "winStatsTimeline.cxx" +#include "winStatsServer.cxx" +#include "winStatsStripChart.cxx" diff --git a/pandatool/src/xfile/CMakeLists.txt b/pandatool/src/xfile/CMakeLists.txt new file mode 100644 index 00000000..1768ef35 --- /dev/null +++ b/pandatool/src/xfile/CMakeLists.txt @@ -0,0 +1,56 @@ +set(P3XFILE_HEADERS + config_xfile.h + standard_templates.h + windowsGuid.h windowsGuid.I + xFileArrayDef.h xFileArrayDef.I + xFileDataDef.h xFileDataDef.I + xFileDataNode.h xFileDataNode.I + xFileDataNodeReference.h xFileDataNodeReference.I + xFileDataNodeTemplate.h xFileDataNodeTemplate.I + xFileDataObjectArray.h xFileDataObjectArray.I + xFileDataObjectDouble.h xFileDataObjectDouble.I + xFileDataObject.h xFileDataObject.I + xFileDataObjectInteger.h xFileDataObjectInteger.I + xFileDataObjectString.h xFileDataObjectString.I + xFile.h xFile.I + xFileNode.h xFileNode.I + xFileParseData.h xFileParseData.I + xFileTemplate.h xFileTemplate.I + xLexerDefs.h + xParserDefs.h +) + +set(P3XFILE_SOURCES + config_xfile.cxx + standard_templates.cxx + windowsGuid.cxx + xFileArrayDef.cxx + xFile.cxx + xFileDataDef.cxx + xFileDataNode.cxx + xFileDataNodeReference.cxx + xFileDataNodeTemplate.cxx + xFileDataObjectArray.cxx + xFileDataObject.cxx + xFileDataObjectDouble.cxx + xFileDataObjectInteger.cxx + xFileDataObjectString.cxx + xFileNode.cxx + xFileParseData.cxx + xFileTemplate.cxx +) + +set(P3XFILE_PARSER_SOURCES + xParser.cxx + xLexer.cxx +) + +add_bison_target(xParser.cxx xParser.yxx DEFINES xParser.h PREFIX xyy) +add_flex_target(xLexer.cxx xLexer.lxx CASE_INSENSITIVE PREFIX xyy) + +composite_sources(p3xfile P3XFILE_SOURCES) +add_library(p3xfile STATIC ${P3XFILE_HEADERS} ${P3XFILE_SOURCES} ${P3XFILE_PARSER_SOURCES}) +target_link_libraries(p3xfile p3pandatoolbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/xfile/config_xfile.cxx b/pandatool/src/xfile/config_xfile.cxx new file mode 100644 index 00000000..b3f878c0 --- /dev/null +++ b/pandatool/src/xfile/config_xfile.cxx @@ -0,0 +1,68 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_xfile.cxx + * @author drose + * @date 2000-08-24 + */ + +#include "config_xfile.h" +#include "xFile.h" +#include "xFileDataDef.h" +#include "xFileDataObject.h" +#include "xFileDataObjectArray.h" +#include "xFileDataObjectDouble.h" +#include "xFileDataObjectInteger.h" +#include "xFileDataObjectString.h" +#include "xFileDataNode.h" +#include "xFileDataNodeReference.h" +#include "xFileDataNodeTemplate.h" +#include "xFileNode.h" +#include "xFileTemplate.h" + +#include "dconfig.h" + +Configure(config_xfile); +NotifyCategoryDef(xfile, ""); + +// This is set true, typically by the user's command-line options, to indicate +// that when a X file is generated it should include all geometry in one big +// mesh, instead of preserving the hierarchy from the source egg file. +bool xfile_one_mesh = false; + +ConfigureFn(config_xfile) { + init_libxfile(); +} + +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ +void +init_libxfile() { + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + XFile::init_type(); + XFileDataDef::init_type(); + XFileDataObject::init_type(); + XFileDataObjectArray::init_type(); + XFileDataObjectDouble::init_type(); + XFileDataObjectInteger::init_type(); + XFileDataObjectString::init_type(); + XFileDataNode::init_type(); + XFileDataNodeReference::init_type(); + XFileDataNodeTemplate::init_type(); + XFileNode::init_type(); + XFileTemplate::init_type(); +} diff --git a/pandatool/src/xfile/config_xfile.h b/pandatool/src/xfile/config_xfile.h new file mode 100644 index 00000000..770d058f --- /dev/null +++ b/pandatool/src/xfile/config_xfile.h @@ -0,0 +1,27 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file config_xfile.h + * @author drose + * @date 2001-06-22 + */ + +#ifndef CONFIG_XFILE_H +#define CONFIG_XFILE_H + +#include "pandatoolbase.h" + +#include "notifyCategoryProxy.h" + +NotifyCategoryDeclNoExport(xfile); + +extern bool xfile_one_mesh; + +extern void init_libxfile(); + +#endif diff --git a/pandatool/src/xfile/p3xfile_composite1.cxx b/pandatool/src/xfile/p3xfile_composite1.cxx new file mode 100644 index 00000000..3a4f74d9 --- /dev/null +++ b/pandatool/src/xfile/p3xfile_composite1.cxx @@ -0,0 +1,17 @@ +#include "config_xfile.cxx" +#include "standard_templates.cxx" +#include "windowsGuid.cxx" +#include "xFile.cxx" +#include "xFileArrayDef.cxx" +#include "xFileDataDef.cxx" +#include "xFileDataNode.cxx" +#include "xFileDataNodeReference.cxx" +#include "xFileDataNodeTemplate.cxx" +#include "xFileDataObject.cxx" +#include "xFileDataObjectArray.cxx" +#include "xFileDataObjectDouble.cxx" +#include "xFileDataObjectInteger.cxx" +#include "xFileDataObjectString.cxx" +#include "xFileNode.cxx" +#include "xFileParseData.cxx" +#include "xFileTemplate.cxx" diff --git a/pandatool/src/xfile/standardTemplates.x b/pandatool/src/xfile/standardTemplates.x new file mode 100644 index 00000000..20199a32 --- /dev/null +++ b/pandatool/src/xfile/standardTemplates.x @@ -0,0 +1,274 @@ +xof 0303txt 0032 + +# This file contains the standard template definitions for Direct3D +# Retained Mode. I extracted these from the DirectX API via something +# like the following code: +# +# #include +# +# LPDIRECTXFILE dx_file; +# LPDIRECTXFILESAVEOBJECT dx_file_save; +# HRESULT hr; +# +# hr = DirectXFileCreate(&dx_file); +# hr = dx_file->RegisterTemplates(D3DRM_XTEMPLATES, D3DRM_XTEMPLATE_BYTES); +# hr = dx_file->CreateSaveObject("filename.x", DXFILEFORMAT_TEXT, +# &dx_file_save); +# static const GUID *temps[] = { +# &TID_D3DRMInfo, +# &TID_D3DRMMesh, +# &TID_D3DRMVector, +# ... +# }; +# static const int num_temps = sizeof(temps) / sizeof(temps[0]); +# hr = dx_file_save->SaveTemplates(num_temps, temps); +# + + +template Header { + <3d82ab43-62da-11cf-ab39-0020af71e433> + WORD major; + WORD minor; + DWORD flags; +} + +template Vector { + <3d82ab5e-62da-11cf-ab39-0020af71e433> + FLOAT x; + FLOAT y; + FLOAT z; +} + +template MeshFace { + <3d82ab5f-62da-11cf-ab39-0020af71e433> + DWORD nFaceVertexIndices; + array DWORD faceVertexIndices[nFaceVertexIndices]; +} + +template Mesh { + <3d82ab44-62da-11cf-ab39-0020af71e433> + DWORD nVertices; + array Vector vertices[nVertices]; + DWORD nFaces; + array MeshFace faces[nFaces]; + [...] +} + +template ColorRGBA { + <35ff44e0-6c7c-11cf-8f52-0040333594a3> + FLOAT red; + FLOAT green; + FLOAT blue; + FLOAT alpha; +} + +template ColorRGB { + + FLOAT red; + FLOAT green; + FLOAT blue; +} + +template Material { + <3d82ab4d-62da-11cf-ab39-0020af71e433> + ColorRGBA faceColor; + FLOAT power; + ColorRGB specularColor; + ColorRGB emissiveColor; + [...] +} + +template Frame { + <3d82ab46-62da-11cf-ab39-0020af71e433> + [...] +} + +template Matrix4x4 { + + array FLOAT matrix[16]; +} + +template FrameTransformMatrix { + + Matrix4x4 frameMatrix; +} + +template MeshMaterialList { + + DWORD nMaterials; + DWORD nFaceIndexes; + array DWORD faceIndexes[nFaceIndexes]; + [Material <3d82ab4d-62da-11cf-ab39-0020af71e433>] +} + +template Coords2d { + + FLOAT u; + FLOAT v; +} + +template MeshTextureCoords { + + DWORD nTextureCoords; + array Coords2d textureCoords[nTextureCoords]; +} + +template MeshNormals { + + DWORD nNormals; + array Vector normals[nNormals]; + DWORD nFaceNormals; + array MeshFace faceNormals[nFaceNormals]; +} + +template Animation { + <3d82ab4f-62da-11cf-ab39-0020af71e433> + [...] +} + +template AnimationSet { + <3d82ab50-62da-11cf-ab39-0020af71e433> + [Animation <3d82ab4f-62da-11cf-ab39-0020af71e433>] +} + +template FloatKeys { + <10dd46a9-775b-11cf-8f52-0040333594a3> + DWORD nValues; + array FLOAT values[nValues]; +} + +template TimedFloatKeys { + + DWORD time; + FloatKeys tfkeys; +} + +template AnimationKey { + <10dd46a8-775b-11cf-8f52-0040333594a3> + DWORD keyType; + DWORD nKeys; + array TimedFloatKeys keys[nKeys]; +} + +template Guid { + + DWORD data1; + WORD data2; + WORD data3; + array UCHAR data4[8]; +} + +template TextureFilename { + + STRING filename; +} + +template IndexedColor { + <1630b820-7842-11cf-8f52-0040333594a3> + DWORD index; + ColorRGBA indexColor; +} + +template MeshVertexColors { + <1630b821-7842-11cf-8f52-0040333594a3> + DWORD nVertexColors; + array IndexedColor vertexColors[nVertexColors]; +} + +template Boolean { + <537da6a0-ca37-11d0-941c-0080c80cfa7b> + DWORD truefalse; +} + +template MaterialWrap { + <4885ae60-78e8-11cf-8f52-0040333594a3> + Boolean u; + Boolean v; +} + +template Boolean2d { + <4885ae63-78e8-11cf-8f52-0040333594a3> + Boolean u; + Boolean v; +} + +template MeshFaceWraps { + + DWORD nFaceWrapValues; + array Boolean2d faceWrapValues[nFaceWrapValues]; +} + +template AnimationOptions { + + DWORD openclosed; + DWORD positionquality; +} + +# Since I don't have documentation on the precise semantic meaning of +# the BINARY keyword, I can't parse the following yet. + +#template InlineData { +# <3a23eea0-94b1-11d0-ab39-0020af71e433> +# [BINARY] +#} +# +#template Url { +# <3a23eea1-94b1-11d0-ab39-0020af71e433> +# DWORD nUrls; +# array STRING urls[nUrls]; +#} +# +#template ProgressiveMesh { +# <8a63c360-997d-11d0-941c-0080c80cfa7b> +# [Url <3a23eea1-94b1-11d0-ab39-0020af71e433>, InlineData <3a23eea0-94b1-11d0-ab39-0020af71e433>] +#} + +template ExternalVisual { + <98116aa0-bdba-11d1-82c0-00a0c9697271> + Guid guidExternalVisual; + [...] +} + +template StringProperty { + <7f0f21e0-bfe1-11d1-82c0-00a0c9697271> + STRING key; + STRING value; +} + +template PropertyBag { + <7f0f21e1-bfe1-11d1-82c0-00a0c9697271> + [StringProperty <7f0f21e0-bfe1-11d1-82c0-00a0c9697271>] +} + +template RightHanded { + <7f5d5ea0-d53a-11d1-82c0-00a0c9697271> + DWORD bRightHanded; +} + +template XSkinMeshHeader { + <3cf169ce-ff7c-44ab-93c0-f78f62d172e2> + WORD nMaxSkinWeightsPerVertex; + WORD nMaxSkinWeightsPerFace; + WORD nBones; +} + +template VertexDuplicationIndices { + + DWORD nIndices; + DWORD nOriginalVertices; + array DWORD indices[nIndices]; +} + +template SkinWeights { + <6f0d123b-bad2-4167-a0d0-80224f25fabb> + STRING transformNodeName; + DWORD nWeights; + array DWORD vertexIndices[nWeights]; + array FLOAT weights[nWeights]; + Matrix4x4 matrixOffset; +} + +template AnimTicksPerSecond { + <9E415A43-7BA6-4a73-8743-B73D47E88476> + DWORD AnimTicksPerSecond; +} diff --git a/pandatool/src/xfile/standardTemplates.x.c b/pandatool/src/xfile/standardTemplates.x.c new file mode 100644 index 00000000..d4c8213d --- /dev/null +++ b/pandatool/src/xfile/standardTemplates.x.c @@ -0,0 +1,515 @@ + +/* + * This table was generated by the command: + * + * bin2c -n standard_templates_data -o standardTemplates.x.c standardTemplates.x + */ + +#include + +const unsigned char standard_templates_data[] = { + 0x78, 0x6f, 0x66, 0x20, 0x30, 0x33, 0x30, 0x33, 0x74, 0x78, 0x74, + 0x20, 0x30, 0x30, 0x33, 0x32, 0x0a, 0x0a, 0x23, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x20, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x33, 0x44, 0x0a, 0x23, 0x20, + 0x52, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x6f, + 0x64, 0x65, 0x2e, 0x20, 0x20, 0x49, 0x20, 0x65, 0x78, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, + 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x44, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x58, 0x20, 0x41, 0x50, 0x49, 0x20, + 0x76, 0x69, 0x61, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x0a, 0x23, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, + 0x67, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x0a, 0x23, 0x0a, 0x23, + 0x20, 0x20, 0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, + 0x3c, 0x72, 0x6d, 0x78, 0x66, 0x74, 0x6d, 0x70, 0x6c, 0x2e, 0x68, + 0x3e, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x20, 0x4c, 0x50, 0x44, 0x49, + 0x52, 0x45, 0x43, 0x54, 0x58, 0x46, 0x49, 0x4c, 0x45, 0x20, 0x64, + 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x3b, 0x0a, 0x23, 0x20, 0x20, + 0x4c, 0x50, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x58, 0x46, 0x49, + 0x4c, 0x45, 0x53, 0x41, 0x56, 0x45, 0x4f, 0x42, 0x4a, 0x45, 0x43, + 0x54, 0x20, 0x64, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, + 0x61, 0x76, 0x65, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x48, 0x52, 0x45, + 0x53, 0x55, 0x4c, 0x54, 0x20, 0x68, 0x72, 0x3b, 0x0a, 0x23, 0x0a, + 0x23, 0x20, 0x20, 0x68, 0x72, 0x20, 0x3d, 0x20, 0x44, 0x69, 0x72, + 0x65, 0x63, 0x74, 0x58, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x28, 0x26, 0x64, 0x78, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x29, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x68, 0x72, 0x20, 0x3d, + 0x20, 0x64, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x3e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x73, 0x28, 0x44, 0x33, 0x44, 0x52, 0x4d, + 0x5f, 0x58, 0x54, 0x45, 0x4d, 0x50, 0x4c, 0x41, 0x54, 0x45, 0x53, + 0x2c, 0x20, 0x44, 0x33, 0x44, 0x52, 0x4d, 0x5f, 0x58, 0x54, 0x45, + 0x4d, 0x50, 0x4c, 0x41, 0x54, 0x45, 0x5f, 0x42, 0x59, 0x54, 0x45, + 0x53, 0x29, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x68, 0x72, 0x20, 0x3d, + 0x20, 0x64, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x3e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x61, 0x76, 0x65, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x2e, 0x78, 0x22, 0x2c, 0x20, 0x44, 0x58, 0x46, + 0x49, 0x4c, 0x45, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x5f, 0x54, + 0x45, 0x58, 0x54, 0x2c, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x26, 0x64, 0x78, 0x5f, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x61, 0x76, 0x65, 0x29, 0x3b, + 0x0a, 0x23, 0x20, 0x20, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x47, 0x55, 0x49, 0x44, 0x20, + 0x2a, 0x74, 0x65, 0x6d, 0x70, 0x73, 0x5b, 0x5d, 0x20, 0x3d, 0x20, + 0x7b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x26, 0x54, 0x49, 0x44, + 0x5f, 0x44, 0x33, 0x44, 0x52, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x2c, + 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x26, 0x54, 0x49, 0x44, 0x5f, + 0x44, 0x33, 0x44, 0x52, 0x4d, 0x4d, 0x65, 0x73, 0x68, 0x2c, 0x0a, + 0x23, 0x20, 0x20, 0x20, 0x20, 0x26, 0x54, 0x49, 0x44, 0x5f, 0x44, + 0x33, 0x44, 0x52, 0x4d, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2c, + 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x23, + 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x63, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x69, + 0x6e, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x65, 0x6d, 0x70, + 0x73, 0x20, 0x3d, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x6f, 0x66, 0x28, + 0x74, 0x65, 0x6d, 0x70, 0x73, 0x29, 0x20, 0x2f, 0x20, 0x73, 0x69, + 0x7a, 0x65, 0x6f, 0x66, 0x28, 0x74, 0x65, 0x6d, 0x70, 0x73, 0x5b, + 0x30, 0x5d, 0x29, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x68, 0x72, 0x20, + 0x3d, 0x20, 0x64, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, + 0x61, 0x76, 0x65, 0x2d, 0x3e, 0x53, 0x61, 0x76, 0x65, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x28, 0x6e, 0x75, 0x6d, + 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x73, 0x2c, 0x20, 0x74, 0x65, 0x6d, + 0x70, 0x73, 0x29, 0x3b, 0x0a, 0x23, 0x0a, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x33, 0x64, 0x38, 0x32, + 0x61, 0x62, 0x34, 0x33, 0x2d, 0x36, 0x32, 0x64, 0x61, 0x2d, 0x31, + 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, + 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, + 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x3b, 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, + 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x56, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x33, 0x64, + 0x38, 0x32, 0x61, 0x62, 0x35, 0x65, 0x2d, 0x36, 0x32, 0x64, 0x61, + 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, + 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, + 0x33, 0x3e, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x78, + 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x79, 0x3b, + 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x7a, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x4d, 0x65, 0x73, 0x68, 0x46, 0x61, 0x63, 0x65, 0x20, 0x7b, + 0x0a, 0x20, 0x3c, 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, 0x35, 0x66, + 0x2d, 0x36, 0x32, 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, + 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, + 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, + 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x46, 0x61, 0x63, 0x65, 0x56, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, + 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x44, 0x57, + 0x4f, 0x52, 0x44, 0x20, 0x66, 0x61, 0x63, 0x65, 0x56, 0x65, 0x72, + 0x74, 0x65, 0x78, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x5b, + 0x6e, 0x46, 0x61, 0x63, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x5d, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x4d, 0x65, 0x73, 0x68, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x33, 0x64, + 0x38, 0x32, 0x61, 0x62, 0x34, 0x34, 0x2d, 0x36, 0x32, 0x64, 0x61, + 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, + 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, + 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, + 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x0a, 0x20, + 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x20, 0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x65, 0x73, 0x5b, + 0x6e, 0x56, 0x65, 0x72, 0x74, 0x69, 0x63, 0x65, 0x73, 0x5d, 0x3b, + 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x46, 0x61, + 0x63, 0x65, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, + 0x20, 0x4d, 0x65, 0x73, 0x68, 0x46, 0x61, 0x63, 0x65, 0x20, 0x66, + 0x61, 0x63, 0x65, 0x73, 0x5b, 0x6e, 0x46, 0x61, 0x63, 0x65, 0x73, + 0x5d, 0x3b, 0x0a, 0x20, 0x5b, 0x2e, 0x2e, 0x2e, 0x5d, 0x0a, 0x7d, + 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x52, 0x47, 0x42, 0x41, 0x20, 0x7b, + 0x0a, 0x20, 0x3c, 0x33, 0x35, 0x66, 0x66, 0x34, 0x34, 0x65, 0x30, + 0x2d, 0x36, 0x63, 0x37, 0x63, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, + 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, + 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x46, 0x4c, + 0x4f, 0x41, 0x54, 0x20, 0x72, 0x65, 0x64, 0x3b, 0x0a, 0x20, 0x46, + 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x3b, + 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x62, 0x6c, 0x75, + 0x65, 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x52, 0x47, 0x42, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x64, 0x33, + 0x65, 0x31, 0x36, 0x65, 0x38, 0x31, 0x2d, 0x37, 0x38, 0x33, 0x35, + 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, + 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, + 0x33, 0x3e, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x72, + 0x65, 0x64, 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, + 0x67, 0x72, 0x65, 0x65, 0x6e, 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, + 0x41, 0x54, 0x20, 0x62, 0x6c, 0x75, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x4d, + 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x7b, 0x0a, 0x20, + 0x3c, 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, 0x34, 0x64, 0x2d, 0x36, + 0x32, 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, + 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, + 0x65, 0x34, 0x33, 0x33, 0x3e, 0x0a, 0x20, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x52, 0x47, 0x42, 0x41, 0x20, 0x66, 0x61, 0x63, 0x65, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, + 0x54, 0x20, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x43, + 0x6f, 0x6c, 0x6f, 0x72, 0x52, 0x47, 0x42, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x75, 0x6c, 0x61, 0x72, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, + 0x0a, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x52, 0x47, 0x42, 0x20, + 0x65, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x3b, 0x0a, 0x20, 0x5b, 0x2e, 0x2e, 0x2e, 0x5d, 0x0a, + 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x3c, + 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, 0x34, 0x36, 0x2d, 0x36, 0x32, + 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, + 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, + 0x34, 0x33, 0x33, 0x3e, 0x0a, 0x20, 0x5b, 0x2e, 0x2e, 0x2e, 0x5d, + 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x20, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x34, 0x78, 0x34, + 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x66, 0x36, 0x66, 0x32, 0x33, 0x66, + 0x34, 0x35, 0x2d, 0x37, 0x36, 0x38, 0x36, 0x2d, 0x31, 0x31, 0x63, + 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, + 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, + 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, + 0x20, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x5b, 0x31, 0x36, 0x5d, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x4d, 0x61, 0x74, 0x72, 0x69, + 0x78, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x66, 0x36, 0x66, 0x32, 0x33, + 0x66, 0x34, 0x31, 0x2d, 0x37, 0x36, 0x38, 0x36, 0x2d, 0x31, 0x31, + 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, + 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, + 0x20, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x34, 0x78, 0x34, 0x20, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x20, 0x4d, 0x65, 0x73, 0x68, 0x4d, 0x61, 0x74, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x3c, 0x66, 0x36, 0x66, 0x32, 0x33, 0x66, 0x34, 0x32, 0x2d, + 0x37, 0x36, 0x38, 0x36, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, + 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, + 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, + 0x52, 0x44, 0x20, 0x6e, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x73, 0x3b, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, + 0x6e, 0x46, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, + 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x44, + 0x57, 0x4f, 0x52, 0x44, 0x20, 0x66, 0x61, 0x63, 0x65, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x65, 0x73, 0x5b, 0x6e, 0x46, 0x61, 0x63, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x5d, 0x3b, 0x0a, 0x20, + 0x5b, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x3c, + 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, 0x34, 0x64, 0x2d, 0x36, 0x32, + 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, + 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, + 0x34, 0x33, 0x33, 0x3e, 0x5d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6f, 0x72, + 0x64, 0x73, 0x32, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x66, 0x36, + 0x66, 0x32, 0x33, 0x66, 0x34, 0x34, 0x2d, 0x37, 0x36, 0x38, 0x36, + 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, + 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, + 0x33, 0x3e, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x75, + 0x3b, 0x0a, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, 0x76, 0x3b, + 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x20, 0x4d, 0x65, 0x73, 0x68, 0x54, 0x65, 0x78, 0x74, 0x75, + 0x72, 0x65, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x73, 0x20, 0x7b, 0x0a, + 0x20, 0x3c, 0x66, 0x36, 0x66, 0x32, 0x33, 0x66, 0x34, 0x30, 0x2d, + 0x37, 0x36, 0x38, 0x36, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, + 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, + 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, + 0x52, 0x44, 0x20, 0x6e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, + 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, + 0x72, 0x61, 0x79, 0x20, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x73, 0x32, + 0x64, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, + 0x6f, 0x72, 0x64, 0x73, 0x5b, 0x6e, 0x54, 0x65, 0x78, 0x74, 0x75, + 0x72, 0x65, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x73, 0x5d, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x4d, 0x65, 0x73, 0x68, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, + 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x66, 0x36, 0x66, 0x32, 0x33, + 0x66, 0x34, 0x33, 0x2d, 0x37, 0x36, 0x38, 0x36, 0x2d, 0x31, 0x31, + 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, + 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, + 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x4e, 0x6f, 0x72, + 0x6d, 0x61, 0x6c, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, + 0x79, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x6e, 0x6f, + 0x72, 0x6d, 0x61, 0x6c, 0x73, 0x5b, 0x6e, 0x4e, 0x6f, 0x72, 0x6d, + 0x61, 0x6c, 0x73, 0x5d, 0x3b, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, + 0x44, 0x20, 0x6e, 0x46, 0x61, 0x63, 0x65, 0x4e, 0x6f, 0x72, 0x6d, + 0x61, 0x6c, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, + 0x20, 0x4d, 0x65, 0x73, 0x68, 0x46, 0x61, 0x63, 0x65, 0x20, 0x66, + 0x61, 0x63, 0x65, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x73, 0x5b, + 0x6e, 0x46, 0x61, 0x63, 0x65, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, + 0x73, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x20, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x33, 0x64, 0x38, + 0x32, 0x61, 0x62, 0x34, 0x66, 0x2d, 0x36, 0x32, 0x64, 0x61, 0x2d, + 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, + 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, + 0x3e, 0x0a, 0x20, 0x5b, 0x2e, 0x2e, 0x2e, 0x5d, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x41, + 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, + 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, + 0x35, 0x30, 0x2d, 0x36, 0x32, 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, + 0x66, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, + 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, 0x0a, 0x20, + 0x5b, 0x41, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x3c, 0x33, 0x64, 0x38, 0x32, 0x61, 0x62, 0x34, 0x66, 0x2d, 0x36, + 0x32, 0x64, 0x61, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x61, 0x62, + 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, + 0x65, 0x34, 0x33, 0x33, 0x3e, 0x5d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x3c, + 0x31, 0x30, 0x64, 0x64, 0x34, 0x36, 0x61, 0x39, 0x2d, 0x37, 0x37, + 0x35, 0x62, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, + 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, + 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, + 0x20, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x3b, 0x0a, 0x20, + 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, + 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5b, 0x6e, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, + 0x65, 0x64, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4b, 0x65, 0x79, 0x73, + 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x66, 0x34, 0x30, 0x36, 0x62, 0x31, + 0x38, 0x30, 0x2d, 0x37, 0x62, 0x33, 0x62, 0x2d, 0x31, 0x31, 0x63, + 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, + 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, + 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x3b, + 0x0a, 0x20, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x4b, 0x65, 0x79, 0x73, + 0x20, 0x74, 0x66, 0x6b, 0x65, 0x79, 0x73, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x41, + 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, + 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x31, 0x30, 0x64, 0x64, 0x34, 0x36, + 0x61, 0x38, 0x2d, 0x37, 0x37, 0x35, 0x62, 0x2d, 0x31, 0x31, 0x63, + 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, + 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, + 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6b, 0x65, 0x79, 0x54, 0x79, + 0x70, 0x65, 0x3b, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, + 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x64, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x20, 0x6b, 0x65, 0x79, 0x73, + 0x5b, 0x6e, 0x4b, 0x65, 0x79, 0x73, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x47, + 0x75, 0x69, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x61, 0x34, 0x32, + 0x37, 0x39, 0x30, 0x65, 0x30, 0x2d, 0x37, 0x38, 0x31, 0x30, 0x2d, + 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, + 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, + 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x31, 0x3b, 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, + 0x64, 0x61, 0x74, 0x61, 0x32, 0x3b, 0x0a, 0x20, 0x57, 0x4f, 0x52, + 0x44, 0x20, 0x64, 0x61, 0x74, 0x61, 0x33, 0x3b, 0x0a, 0x20, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x20, 0x55, 0x43, 0x48, 0x41, 0x52, 0x20, + 0x64, 0x61, 0x74, 0x61, 0x34, 0x5b, 0x38, 0x5d, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x46, 0x69, 0x6c, 0x65, + 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x61, 0x34, + 0x32, 0x37, 0x39, 0x30, 0x65, 0x31, 0x2d, 0x37, 0x38, 0x31, 0x30, + 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, + 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, + 0x33, 0x3e, 0x0a, 0x20, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x20, + 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x31, 0x36, 0x33, 0x30, 0x62, + 0x38, 0x32, 0x30, 0x2d, 0x37, 0x38, 0x34, 0x32, 0x2d, 0x31, 0x31, + 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, + 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, + 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x3b, 0x0a, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x52, 0x47, + 0x42, 0x41, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x20, 0x4d, 0x65, 0x73, 0x68, 0x56, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x20, + 0x7b, 0x0a, 0x20, 0x3c, 0x31, 0x36, 0x33, 0x30, 0x62, 0x38, 0x32, + 0x31, 0x2d, 0x37, 0x38, 0x34, 0x32, 0x2d, 0x31, 0x31, 0x63, 0x66, + 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, + 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x44, + 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x56, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x3b, 0x0a, 0x20, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x20, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, + 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x76, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x5b, 0x6e, 0x56, + 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x73, + 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x20, 0x42, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, + 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x35, 0x33, 0x37, 0x64, 0x61, 0x36, + 0x61, 0x30, 0x2d, 0x63, 0x61, 0x33, 0x37, 0x2d, 0x31, 0x31, 0x64, + 0x30, 0x2d, 0x39, 0x34, 0x31, 0x63, 0x2d, 0x30, 0x30, 0x38, 0x30, + 0x63, 0x38, 0x30, 0x63, 0x66, 0x61, 0x37, 0x62, 0x3e, 0x0a, 0x20, + 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x74, 0x72, 0x75, 0x65, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x4d, 0x61, 0x74, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x57, 0x72, 0x61, 0x70, 0x20, 0x7b, 0x0a, + 0x20, 0x3c, 0x34, 0x38, 0x38, 0x35, 0x61, 0x65, 0x36, 0x30, 0x2d, + 0x37, 0x38, 0x65, 0x38, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, + 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, + 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x42, 0x6f, 0x6f, + 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x75, 0x3b, 0x0a, 0x20, 0x42, 0x6f, + 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x76, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x42, + 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x32, 0x64, 0x20, 0x7b, 0x0a, + 0x20, 0x3c, 0x34, 0x38, 0x38, 0x35, 0x61, 0x65, 0x36, 0x33, 0x2d, + 0x37, 0x38, 0x65, 0x38, 0x2d, 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, + 0x66, 0x35, 0x32, 0x2d, 0x30, 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, + 0x35, 0x39, 0x34, 0x61, 0x33, 0x3e, 0x0a, 0x20, 0x42, 0x6f, 0x6f, + 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x75, 0x3b, 0x0a, 0x20, 0x42, 0x6f, + 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x20, 0x76, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x4d, + 0x65, 0x73, 0x68, 0x46, 0x61, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, + 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x65, 0x64, 0x31, 0x65, 0x63, + 0x35, 0x63, 0x30, 0x2d, 0x63, 0x30, 0x61, 0x38, 0x2d, 0x31, 0x31, + 0x64, 0x30, 0x2d, 0x39, 0x34, 0x31, 0x63, 0x2d, 0x30, 0x30, 0x38, + 0x30, 0x63, 0x38, 0x30, 0x63, 0x66, 0x61, 0x37, 0x62, 0x3e, 0x0a, + 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x46, 0x61, 0x63, + 0x65, 0x57, 0x72, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x42, 0x6f, + 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x32, 0x64, 0x20, 0x66, 0x61, 0x63, + 0x65, 0x57, 0x72, 0x61, 0x70, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x5b, 0x6e, 0x46, 0x61, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x41, 0x6e, + 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x65, 0x32, 0x62, + 0x66, 0x35, 0x36, 0x63, 0x30, 0x2d, 0x38, 0x34, 0x30, 0x66, 0x2d, + 0x31, 0x31, 0x63, 0x66, 0x2d, 0x38, 0x66, 0x35, 0x32, 0x2d, 0x30, + 0x30, 0x34, 0x30, 0x33, 0x33, 0x33, 0x35, 0x39, 0x34, 0x61, 0x33, + 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6f, 0x70, + 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x3b, 0x0a, 0x20, + 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x3b, + 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x20, 0x53, 0x69, 0x6e, 0x63, 0x65, + 0x20, 0x49, 0x20, 0x64, 0x6f, 0x6e, 0x27, 0x74, 0x20, 0x68, 0x61, + 0x76, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x65, 0x20, 0x73, + 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x20, 0x6d, 0x65, 0x61, + 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x0a, 0x23, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x20, 0x6b, + 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x49, 0x20, 0x63, + 0x61, 0x6e, 0x27, 0x74, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, + 0x6e, 0x67, 0x20, 0x79, 0x65, 0x74, 0x2e, 0x0a, 0x0a, 0x23, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x49, 0x6e, 0x6c, + 0x69, 0x6e, 0x65, 0x44, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x23, + 0x20, 0x3c, 0x33, 0x61, 0x32, 0x33, 0x65, 0x65, 0x61, 0x30, 0x2d, + 0x39, 0x34, 0x62, 0x31, 0x2d, 0x31, 0x31, 0x64, 0x30, 0x2d, 0x61, + 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, + 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, 0x0a, 0x23, 0x20, 0x5b, 0x42, + 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5d, 0x0a, 0x23, 0x7d, 0x0a, 0x23, + 0x0a, 0x23, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x55, 0x72, 0x6c, 0x20, 0x7b, 0x0a, 0x23, 0x20, 0x3c, 0x33, 0x61, + 0x32, 0x33, 0x65, 0x65, 0x61, 0x31, 0x2d, 0x39, 0x34, 0x62, 0x31, + 0x2d, 0x31, 0x31, 0x64, 0x30, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, + 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, + 0x33, 0x3e, 0x0a, 0x23, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, + 0x6e, 0x55, 0x72, 0x6c, 0x73, 0x3b, 0x0a, 0x23, 0x20, 0x61, 0x72, + 0x72, 0x61, 0x79, 0x20, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x20, + 0x75, 0x72, 0x6c, 0x73, 0x5b, 0x6e, 0x55, 0x72, 0x6c, 0x73, 0x5d, + 0x3b, 0x0a, 0x23, 0x7d, 0x0a, 0x23, 0x0a, 0x23, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x65, 0x73, 0x68, 0x20, + 0x7b, 0x0a, 0x23, 0x20, 0x3c, 0x38, 0x61, 0x36, 0x33, 0x63, 0x33, + 0x36, 0x30, 0x2d, 0x39, 0x39, 0x37, 0x64, 0x2d, 0x31, 0x31, 0x64, + 0x30, 0x2d, 0x39, 0x34, 0x31, 0x63, 0x2d, 0x30, 0x30, 0x38, 0x30, + 0x63, 0x38, 0x30, 0x63, 0x66, 0x61, 0x37, 0x62, 0x3e, 0x0a, 0x23, + 0x20, 0x5b, 0x55, 0x72, 0x6c, 0x20, 0x3c, 0x33, 0x61, 0x32, 0x33, + 0x65, 0x65, 0x61, 0x31, 0x2d, 0x39, 0x34, 0x62, 0x31, 0x2d, 0x31, + 0x31, 0x64, 0x30, 0x2d, 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, + 0x32, 0x30, 0x61, 0x66, 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, + 0x2c, 0x20, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x44, 0x61, 0x74, + 0x61, 0x20, 0x3c, 0x33, 0x61, 0x32, 0x33, 0x65, 0x65, 0x61, 0x30, + 0x2d, 0x39, 0x34, 0x62, 0x31, 0x2d, 0x31, 0x31, 0x64, 0x30, 0x2d, + 0x61, 0x62, 0x33, 0x39, 0x2d, 0x30, 0x30, 0x32, 0x30, 0x61, 0x66, + 0x37, 0x31, 0x65, 0x34, 0x33, 0x33, 0x3e, 0x5d, 0x0a, 0x23, 0x7d, + 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x20, + 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x56, 0x69, 0x73, + 0x75, 0x61, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x39, 0x38, 0x31, + 0x31, 0x36, 0x61, 0x61, 0x30, 0x2d, 0x62, 0x64, 0x62, 0x61, 0x2d, + 0x31, 0x31, 0x64, 0x31, 0x2d, 0x38, 0x32, 0x63, 0x30, 0x2d, 0x30, + 0x30, 0x61, 0x30, 0x63, 0x39, 0x36, 0x39, 0x37, 0x32, 0x37, 0x31, + 0x3e, 0x0a, 0x20, 0x47, 0x75, 0x69, 0x64, 0x20, 0x67, 0x75, 0x69, + 0x64, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x56, 0x69, + 0x73, 0x75, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x5b, 0x2e, 0x2e, 0x2e, + 0x5d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x20, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x3c, + 0x37, 0x66, 0x30, 0x66, 0x32, 0x31, 0x65, 0x30, 0x2d, 0x62, 0x66, + 0x65, 0x31, 0x2d, 0x31, 0x31, 0x64, 0x31, 0x2d, 0x38, 0x32, 0x63, + 0x30, 0x2d, 0x30, 0x30, 0x61, 0x30, 0x63, 0x39, 0x36, 0x39, 0x37, + 0x32, 0x37, 0x31, 0x3e, 0x0a, 0x20, 0x53, 0x54, 0x52, 0x49, 0x4e, + 0x47, 0x20, 0x6b, 0x65, 0x79, 0x3b, 0x0a, 0x20, 0x53, 0x54, 0x52, + 0x49, 0x4e, 0x47, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x42, 0x61, + 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x37, 0x66, 0x30, 0x66, 0x32, + 0x31, 0x65, 0x31, 0x2d, 0x62, 0x66, 0x65, 0x31, 0x2d, 0x31, 0x31, + 0x64, 0x31, 0x2d, 0x38, 0x32, 0x63, 0x30, 0x2d, 0x30, 0x30, 0x61, + 0x30, 0x63, 0x39, 0x36, 0x39, 0x37, 0x32, 0x37, 0x31, 0x3e, 0x0a, + 0x20, 0x5b, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x79, 0x20, 0x3c, 0x37, 0x66, 0x30, 0x66, + 0x32, 0x31, 0x65, 0x30, 0x2d, 0x62, 0x66, 0x65, 0x31, 0x2d, 0x31, + 0x31, 0x64, 0x31, 0x2d, 0x38, 0x32, 0x63, 0x30, 0x2d, 0x30, 0x30, + 0x61, 0x30, 0x63, 0x39, 0x36, 0x39, 0x37, 0x32, 0x37, 0x31, 0x3e, + 0x5d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0x48, 0x61, 0x6e, + 0x64, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x37, 0x66, 0x35, + 0x64, 0x35, 0x65, 0x61, 0x30, 0x2d, 0x64, 0x35, 0x33, 0x61, 0x2d, + 0x31, 0x31, 0x64, 0x31, 0x2d, 0x38, 0x32, 0x63, 0x30, 0x2d, 0x30, + 0x30, 0x61, 0x30, 0x63, 0x39, 0x36, 0x39, 0x37, 0x32, 0x37, 0x31, + 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x62, 0x52, + 0x69, 0x67, 0x68, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x3b, + 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x20, 0x58, 0x53, 0x6b, 0x69, 0x6e, 0x4d, 0x65, 0x73, 0x68, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x3c, + 0x33, 0x63, 0x66, 0x31, 0x36, 0x39, 0x63, 0x65, 0x2d, 0x66, 0x66, + 0x37, 0x63, 0x2d, 0x34, 0x34, 0x61, 0x62, 0x2d, 0x39, 0x33, 0x63, + 0x30, 0x2d, 0x66, 0x37, 0x38, 0x66, 0x36, 0x32, 0x64, 0x31, 0x37, + 0x32, 0x65, 0x32, 0x3e, 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, + 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x6b, 0x69, 0x6e, 0x57, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x73, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, + 0x65, 0x78, 0x3b, 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, + 0x4d, 0x61, 0x78, 0x53, 0x6b, 0x69, 0x6e, 0x57, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x73, 0x50, 0x65, 0x72, 0x46, 0x61, 0x63, 0x65, 0x3b, + 0x0a, 0x20, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x42, 0x6f, 0x6e, + 0x65, 0x73, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x20, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, + 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, + 0x3c, 0x62, 0x38, 0x64, 0x36, 0x35, 0x35, 0x34, 0x39, 0x2d, 0x64, + 0x37, 0x63, 0x39, 0x2d, 0x34, 0x39, 0x39, 0x35, 0x2d, 0x38, 0x39, + 0x63, 0x66, 0x2d, 0x35, 0x33, 0x61, 0x39, 0x61, 0x38, 0x62, 0x30, + 0x33, 0x31, 0x65, 0x33, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, + 0x44, 0x20, 0x6e, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x3b, + 0x0a, 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x56, 0x65, 0x72, 0x74, 0x69, + 0x63, 0x65, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, + 0x20, 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x69, 0x6e, 0x64, 0x69, + 0x63, 0x65, 0x73, 0x5b, 0x6e, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, + 0x73, 0x5d, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x20, 0x53, 0x6b, 0x69, 0x6e, 0x57, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x3c, 0x36, + 0x66, 0x30, 0x64, 0x31, 0x32, 0x33, 0x62, 0x2d, 0x62, 0x61, 0x64, + 0x32, 0x2d, 0x34, 0x31, 0x36, 0x37, 0x2d, 0x61, 0x30, 0x64, 0x30, + 0x2d, 0x38, 0x30, 0x32, 0x32, 0x34, 0x66, 0x32, 0x35, 0x66, 0x61, + 0x62, 0x62, 0x3e, 0x0a, 0x20, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, + 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x4e, + 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3b, 0x0a, 0x20, 0x44, + 0x57, 0x4f, 0x52, 0x44, 0x20, 0x6e, 0x57, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x73, 0x3b, 0x0a, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, + 0x44, 0x57, 0x4f, 0x52, 0x44, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, + 0x78, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x5b, 0x6e, 0x57, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x5d, 0x3b, 0x0a, 0x20, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x20, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x20, + 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x5b, 0x6e, 0x57, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x5d, 0x3b, 0x0a, 0x20, 0x4d, 0x61, + 0x74, 0x72, 0x69, 0x78, 0x34, 0x78, 0x34, 0x20, 0x6d, 0x61, 0x74, + 0x72, 0x69, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x20, 0x41, 0x6e, 0x69, 0x6d, 0x54, 0x69, 0x63, 0x6b, 0x73, 0x50, + 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x7b, 0x0a, + 0x20, 0x3c, 0x39, 0x45, 0x34, 0x31, 0x35, 0x41, 0x34, 0x33, 0x2d, + 0x37, 0x42, 0x41, 0x36, 0x2d, 0x34, 0x61, 0x37, 0x33, 0x2d, 0x38, + 0x37, 0x34, 0x33, 0x2d, 0x42, 0x37, 0x33, 0x44, 0x34, 0x37, 0x45, + 0x38, 0x38, 0x34, 0x37, 0x36, 0x3e, 0x0a, 0x20, 0x44, 0x57, 0x4f, + 0x52, 0x44, 0x20, 0x41, 0x6e, 0x69, 0x6d, 0x54, 0x69, 0x63, 0x6b, + 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x3b, + 0x0a, 0x7d, 0x20, 0x0a +}; + +const int standard_templates_data_len = 5504; + diff --git a/pandatool/src/xfile/standardTemplates.x.pz.c b/pandatool/src/xfile/standardTemplates.x.pz.c new file mode 100644 index 00000000..4613dc55 --- /dev/null +++ b/pandatool/src/xfile/standardTemplates.x.pz.c @@ -0,0 +1,184 @@ + +/* + * This table was generated by the command: + * + * bin2c -n standard_templates_data -o standardTemplates.x.pz.c standardTemplates.x.pz + */ + +#include + +const unsigned char standard_templates_data[] = { + 0x78, 0x9c, 0xa5, 0x58, 0x6b, 0x6f, 0xdb, 0x38, 0x16, 0xfd, 0xee, + 0x5f, 0x41, 0x4c, 0x80, 0x6e, 0x67, 0x11, 0x65, 0xf5, 0x7e, 0xac, + 0xbb, 0x05, 0xec, 0xda, 0x69, 0xbd, 0x93, 0x17, 0x6c, 0x35, 0xcd, + 0xc0, 0x08, 0x02, 0x4a, 0x24, 0x6d, 0x4d, 0x65, 0xc9, 0x2b, 0xc9, + 0xa9, 0x33, 0x8b, 0xfe, 0xf7, 0xbd, 0x24, 0x25, 0x59, 0x92, 0x93, + 0x95, 0x81, 0x09, 0x82, 0xc4, 0x7c, 0x9d, 0x7b, 0xee, 0xe1, 0xe5, + 0x25, 0xaf, 0xf7, 0x29, 0x43, 0xaa, 0xa1, 0x1a, 0xc5, 0xbe, 0x40, + 0xaa, 0x6a, 0xe8, 0x83, 0xc1, 0x19, 0xf2, 0xd7, 0x51, 0x8e, 0x58, + 0x14, 0x53, 0x14, 0xa6, 0x49, 0x81, 0xa3, 0x24, 0x47, 0xc5, 0x9a, + 0xa2, 0xbc, 0xc0, 0x09, 0xc1, 0x19, 0x41, 0x05, 0xdd, 0x6c, 0x63, + 0x5c, 0x50, 0x44, 0x28, 0x8b, 0x92, 0xa8, 0x88, 0x52, 0x98, 0xc1, + 0xd2, 0x0c, 0x4d, 0xa2, 0x8c, 0x86, 0x85, 0x31, 0x01, 0x8c, 0x39, + 0xe5, 0x0b, 0x29, 0x41, 0xd7, 0x29, 0xa1, 0x17, 0x08, 0xcd, 0x10, + 0xdd, 0x17, 0x19, 0x0e, 0x0b, 0xe8, 0x02, 0xb0, 0x9c, 0x22, 0x96, + 0xa5, 0x1b, 0x81, 0x2b, 0x57, 0x3d, 0xa0, 0xd1, 0xdd, 0x0c, 0x3d, + 0x47, 0x18, 0xe5, 0xe9, 0x86, 0x16, 0xeb, 0x28, 0x59, 0x01, 0x4c, + 0x1c, 0x7d, 0xa7, 0x62, 0x12, 0x4b, 0xe3, 0x38, 0xfd, 0x01, 0x9d, + 0xc0, 0x89, 0xd0, 0x7f, 0x0e, 0xce, 0x60, 0x10, 0x9d, 0x45, 0x49, + 0x18, 0xef, 0x08, 0x45, 0x1f, 0xb2, 0xcd, 0x9e, 0x15, 0xc0, 0xea, + 0x62, 0xfd, 0x51, 0x0e, 0x5d, 0xdd, 0x4d, 0x66, 0xf3, 0xe9, 0x27, + 0xff, 0xe1, 0x72, 0x76, 0x35, 0x45, 0x64, 0xff, 0xc4, 0xfd, 0x19, + 0x1e, 0x8d, 0x2c, 0x46, 0xf7, 0xd3, 0xdb, 0xf1, 0xbf, 0xa1, 0x59, + 0xcd, 0x79, 0xca, 0xf1, 0xb3, 0x9c, 0xf8, 0x65, 0x3e, 0x5d, 0x7c, + 0xbd, 0xf2, 0xd1, 0x3a, 0x1b, 0x4a, 0xcc, 0x75, 0x86, 0xfe, 0x55, + 0xb1, 0xbd, 0x84, 0xa9, 0x9f, 0x32, 0x0a, 0x2a, 0xbc, 0x7f, 0x57, + 0xae, 0xfc, 0x75, 0x58, 0x4f, 0x2a, 0x7b, 0x94, 0x8f, 0x73, 0xba, + 0x8a, 0xf2, 0x82, 0x66, 0x7e, 0x29, 0x59, 0xfe, 0x7e, 0x62, 0x4c, + 0xe6, 0xd7, 0x4f, 0x0f, 0xfe, 0xf4, 0xfa, 0xee, 0x6a, 0xe4, 0x4f, + 0x17, 0xe7, 0xa8, 0xd3, 0xf3, 0x34, 0xfe, 0x1d, 0xba, 0x5f, 0x03, + 0x93, 0xf6, 0x16, 0xc0, 0xef, 0x36, 0xf8, 0x03, 0x48, 0xbc, 0xff, + 0x85, 0xf7, 0x27, 0x78, 0x43, 0x2f, 0xf6, 0xbf, 0x00, 0x8e, 0xf0, + 0xe8, 0xf2, 0x76, 0x7e, 0x3d, 0xf2, 0x9f, 0xfc, 0xe9, 0x83, 0x7f, + 0xce, 0x21, 0xfa, 0x7e, 0xde, 0x35, 0xfd, 0x96, 0x56, 0x61, 0xa3, + 0x8b, 0x28, 0xe4, 0x9b, 0x9f, 0x17, 0xe8, 0xf3, 0xd7, 0xd9, 0x04, + 0xfd, 0x9d, 0x6f, 0x79, 0xbe, 0x7c, 0x04, 0x36, 0xff, 0x95, 0xa0, + 0xef, 0xfc, 0xd9, 0xe4, 0x49, 0x30, 0x9f, 0x25, 0x2c, 0x3d, 0xef, + 0x76, 0x5e, 0xd3, 0x7c, 0x7d, 0xd4, 0x79, 0x0f, 0x9c, 0xd3, 0xac, + 0xec, 0xbe, 0xb8, 0xb8, 0xe0, 0x1f, 0x7e, 0x1e, 0x5b, 0x8c, 0x92, + 0x02, 0x25, 0xbb, 0xcd, 0x93, 0xb0, 0x09, 0x16, 0xf3, 0xe8, 0x4f, + 0x9a, 0xb2, 0xf7, 0xa2, 0xf9, 0x2b, 0xfa, 0x47, 0xab, 0xbd, 0x54, + 0x1f, 0x8f, 0x95, 0x12, 0xae, 0x28, 0x1f, 0xb9, 0x50, 0x07, 0xdd, + 0x6b, 0xc4, 0x73, 0x24, 0x91, 0xf8, 0xa6, 0x0e, 0x06, 0x75, 0x2c, + 0x7f, 0xa1, 0x98, 0xd0, 0x0c, 0xdc, 0x43, 0x1f, 0x0c, 0xe2, 0xea, + 0x38, 0x30, 0x0d, 0xc5, 0xd6, 0x09, 0x56, 0x34, 0x2d, 0x64, 0x0a, + 0x0e, 0x0c, 0x4f, 0x51, 0x55, 0x5d, 0xc5, 0xcc, 0xd1, 0xa8, 0x69, + 0x18, 0x1f, 0x07, 0xe8, 0xdb, 0xed, 0x7c, 0x82, 0x36, 0xf8, 0x8f, + 0x14, 0xe2, 0xa3, 0x6c, 0x44, 0x89, 0x68, 0x4c, 0x44, 0x8b, 0xc5, + 0x78, 0x95, 0x0f, 0x07, 0x3f, 0x1b, 0x46, 0xa4, 0x02, 0x0d, 0x23, + 0x16, 0xed, 0x31, 0x72, 0x79, 0x75, 0x3b, 0xf2, 0xd1, 0x7e, 0x58, + 0x7d, 0x7a, 0xa9, 0x3f, 0xfd, 0xd9, 0x86, 0xe6, 0x8a, 0x5f, 0xe2, + 0x90, 0x36, 0xc1, 0x59, 0x0f, 0xb8, 0xe4, 0x99, 0xf0, 0x65, 0xf7, + 0x34, 0x2b, 0xe8, 0x7e, 0x96, 0x90, 0x28, 0xa4, 0x40, 0x1a, 0xe1, + 0x2c, 0xc3, 0x2f, 0x95, 0x23, 0xdd, 0xf1, 0xe5, 0xf1, 0x92, 0xc7, + 0x63, 0x36, 0x4d, 0x2d, 0xcd, 0xd3, 0x98, 0x70, 0xc8, 0x16, 0x81, + 0x52, 0xb0, 0xe7, 0xb2, 0x7f, 0x59, 0xcf, 0x78, 0x1c, 0xb6, 0xe8, + 0x1f, 0x56, 0xd4, 0x3a, 0x70, 0xd6, 0x25, 0x53, 0x31, 0x7b, 0x09, + 0x11, 0xf7, 0xd8, 0x22, 0xf9, 0x29, 0x8d, 0xd3, 0x6c, 0xfe, 0x79, + 0x3c, 0x92, 0x4c, 0x2d, 0xc6, 0x4c, 0x93, 0xaa, 0x8a, 0x1d, 0x3a, + 0xa1, 0x64, 0xea, 0x32, 0x4b, 0x07, 0xa6, 0xa6, 0x6a, 0x18, 0x86, + 0xe5, 0x99, 0xf8, 0xb0, 0x21, 0x19, 0x25, 0xf5, 0x46, 0xac, 0x32, + 0x4a, 0x93, 0xba, 0x15, 0xc4, 0x3b, 0x5a, 0x37, 0x70, 0xbc, 0x5d, + 0xe3, 0xe1, 0xab, 0x46, 0x85, 0x4d, 0x62, 0x50, 0xcd, 0xa6, 0xae, + 0xa6, 0x38, 0xae, 0x61, 0xfd, 0x55, 0x9b, 0x2d, 0xfd, 0xe1, 0x4f, + 0x16, 0xe1, 0xb8, 0xb9, 0x07, 0xa4, 0x67, 0x0f, 0x0e, 0x72, 0x70, + 0xe9, 0x44, 0xab, 0xb6, 0xb0, 0x4d, 0x7f, 0x50, 0xde, 0xaa, 0xd9, + 0xe7, 0x5b, 0x1a, 0xee, 0x62, 0x9c, 0x55, 0xd3, 0xea, 0x01, 0xba, + 0x89, 0xf2, 0x3c, 0x7a, 0xae, 0xd7, 0x1f, 0xcb, 0x7e, 0x99, 0x41, + 0xc6, 0x6a, 0x12, 0xb3, 0x7b, 0x88, 0x1d, 0x43, 0x80, 0x7b, 0x59, + 0xb4, 0x37, 0xf7, 0xa6, 0x80, 0x61, 0x36, 0xd3, 0x0d, 0x66, 0x5a, + 0x8a, 0x63, 0xbb, 0xf6, 0xdb, 0x2a, 0xca, 0xf8, 0x90, 0xfe, 0x6c, + 0x04, 0xc0, 0x52, 0xb3, 0x3b, 0x71, 0x2b, 0xb8, 0xf9, 0x19, 0x4e, + 0x72, 0xb8, 0xcf, 0x36, 0xd2, 0x4c, 0xd3, 0x86, 0xd6, 0x63, 0xe3, + 0x40, 0x8c, 0x71, 0x24, 0xd9, 0x3c, 0x3e, 0x1a, 0xd5, 0xf6, 0x5c, + 0xc1, 0xe5, 0xd0, 0x84, 0xd7, 0x7b, 0xe0, 0xcb, 0x88, 0xaf, 0x96, + 0xe7, 0xed, 0x43, 0x00, 0x47, 0x91, 0xee, 0x5f, 0x3d, 0xbd, 0xe5, + 0xc8, 0xb2, 0x39, 0x4d, 0x9c, 0x89, 0x3a, 0x4e, 0x4e, 0x0b, 0x92, + 0xee, 0xf9, 0x49, 0x33, 0x92, 0xeb, 0xa4, 0xe9, 0x81, 0xd9, 0xe3, + 0x81, 0x94, 0x7f, 0x57, 0x07, 0xd6, 0xf3, 0xb1, 0x38, 0x3e, 0xbc, + 0x14, 0x76, 0x19, 0x95, 0xe8, 0x4d, 0x6c, 0xf5, 0x34, 0x75, 0x5a, + 0xeb, 0x6b, 0x31, 0x6a, 0xb2, 0x45, 0x73, 0x78, 0xd9, 0x9e, 0xfd, + 0x4a, 0x16, 0xbb, 0x81, 0x38, 0x00, 0xa5, 0x9b, 0x3c, 0x8c, 0xd3, + 0x78, 0x94, 0x2b, 0xbb, 0xb9, 0x2c, 0x91, 0xdd, 0xcb, 0x6a, 0xbc, + 0x93, 0xc9, 0xba, 0xab, 0x5a, 0xf9, 0xec, 0xa6, 0x5a, 0xdb, 0x98, + 0xd9, 0xe1, 0x3c, 0x4a, 0x22, 0x08, 0x6e, 0x78, 0x92, 0x35, 0x4f, + 0x58, 0xdf, 0x45, 0x70, 0x7c, 0xc2, 0x6a, 0x98, 0x05, 0x2d, 0x9a, + 0x57, 0x8a, 0xda, 0x87, 0x74, 0xb0, 0x7f, 0x9a, 0xf1, 0x4e, 0x6e, + 0x88, 0x53, 0x5c, 0xfc, 0x46, 0x5f, 0xa4, 0xde, 0x9a, 0x4a, 0x88, + 0x69, 0x63, 0x4f, 0x71, 0x1c, 0x2b, 0xe8, 0xd5, 0xfb, 0x1e, 0x43, + 0x1a, 0x3c, 0x08, 0x57, 0xc6, 0x97, 0xe8, 0x5c, 0x96, 0x83, 0x1d, + 0xad, 0xfc, 0x68, 0x43, 0x49, 0xdb, 0x24, 0x44, 0x99, 0x1d, 0x68, + 0x2e, 0x84, 0x5a, 0x60, 0xf4, 0x9a, 0x2c, 0x60, 0x3d, 0x8f, 0xe4, + 0x1a, 0xa1, 0x60, 0xdf, 0xe1, 0xdf, 0x1b, 0x1b, 0x02, 0x33, 0x9a, + 0x5e, 0xb9, 0x27, 0x79, 0x05, 0x78, 0xfe, 0xcb, 0x96, 0x1e, 0x62, + 0xe4, 0x37, 0x61, 0xa0, 0xf4, 0xb1, 0xe3, 0x00, 0x37, 0xbe, 0x14, + 0x33, 0x3a, 0x8e, 0x7e, 0xde, 0x45, 0xf2, 0x94, 0x62, 0x53, 0x77, + 0x3c, 0x15, 0x2e, 0x39, 0xc7, 0xd5, 0xd4, 0x3e, 0xdb, 0x04, 0x17, + 0x58, 0xab, 0xde, 0x36, 0xbc, 0xa1, 0x37, 0x1b, 0x46, 0xcd, 0xe2, + 0xeb, 0xa7, 0x2f, 0xa3, 0xb9, 0xe8, 0x33, 0x97, 0x6e, 0x57, 0x62, + 0x79, 0xbc, 0x2e, 0xcb, 0x87, 0x6a, 0x93, 0x84, 0xd6, 0x43, 0x62, + 0xe1, 0xcf, 0x67, 0x37, 0x9f, 0x51, 0xf5, 0xc6, 0x6d, 0xe3, 0xca, + 0x24, 0x46, 0xc4, 0x2d, 0x23, 0x55, 0xb5, 0x0d, 0x35, 0x70, 0x75, + 0xee, 0x19, 0xa4, 0xd1, 0x1e, 0xcf, 0x22, 0xbe, 0x7a, 0xd8, 0xbc, + 0xf2, 0x44, 0x4f, 0x79, 0x67, 0x75, 0x53, 0x80, 0x7c, 0xe8, 0x88, + 0xc1, 0xbc, 0x69, 0x4b, 0x3b, 0xc9, 0x56, 0xd2, 0x5c, 0x5e, 0x6b, + 0xd6, 0xe2, 0xff, 0xdc, 0x98, 0xb1, 0x6c, 0xcd, 0xef, 0xa8, 0x39, + 0x4e, 0xd3, 0x98, 0x62, 0x79, 0xb4, 0x2d, 0xc3, 0x21, 0xd8, 0xc6, + 0xaa, 0x12, 0x62, 0xc3, 0x01, 0x12, 0x44, 0x55, 0x3c, 0x53, 0x0b, + 0x81, 0x84, 0xab, 0x86, 0xf0, 0xcb, 0xb0, 0x13, 0x1c, 0x22, 0x35, + 0xdb, 0x51, 0x06, 0xc9, 0xe2, 0x8d, 0x67, 0xc2, 0xb7, 0x0c, 0x6f, + 0x05, 0xa8, 0xe9, 0xba, 0x16, 0xa6, 0x36, 0x57, 0x91, 0xba, 0x6f, + 0x7b, 0x56, 0xf1, 0xe0, 0x79, 0xbc, 0xfa, 0xfc, 0xfc, 0x2a, 0xd5, + 0xf2, 0x76, 0x28, 0x71, 0x8d, 0xbf, 0x8e, 0x5b, 0xe5, 0x43, 0x4e, + 0x59, 0xee, 0x06, 0x25, 0x1a, 0x0d, 0xad, 0x10, 0x84, 0x50, 0xb1, + 0xdb, 0x27, 0x44, 0x52, 0xad, 0xed, 0x64, 0x8b, 0x03, 0x5d, 0xd6, + 0x9a, 0xb0, 0xec, 0x2c, 0x78, 0x2b, 0xdb, 0xde, 0x6e, 0x65, 0x19, + 0x2c, 0x08, 0xe9, 0x01, 0xb3, 0x6c, 0x20, 0xe4, 0x9a, 0x2a, 0xeb, + 0x0b, 0x8f, 0x74, 0x4b, 0xa1, 0x88, 0x4d, 0x73, 0xf1, 0xb8, 0x93, + 0x5d, 0xdb, 0x34, 0x17, 0x45, 0xf5, 0x7f, 0x76, 0x38, 0x8e, 0x8a, + 0x17, 0x61, 0xf1, 0x0c, 0x2d, 0xa0, 0xd8, 0x85, 0xb8, 0x47, 0x24, + 0x4d, 0xfe, 0x56, 0xa0, 0x35, 0x14, 0x37, 0xf0, 0x31, 0xdc, 0x6d, + 0x68, 0x52, 0xc8, 0x7c, 0x0b, 0xbf, 0xbc, 0x52, 0xde, 0x42, 0x7d, + 0x1a, 0x41, 0x7d, 0x9d, 0xd3, 0x0d, 0x4e, 0x78, 0x55, 0xb5, 0x01, + 0xbf, 0x78, 0xe1, 0x9c, 0x32, 0x40, 0xe1, 0x33, 0xc6, 0xb3, 0x9b, + 0xd1, 0xfc, 0x77, 0x9e, 0x2d, 0x7e, 0xc0, 0xa5, 0x77, 0x0e, 0x98, + 0x21, 0xe6, 0x98, 0x5b, 0x9c, 0xe5, 0xdd, 0x6a, 0xfb, 0x85, 0x16, + 0x17, 0x60, 0xbd, 0x71, 0xee, 0x62, 0x28, 0xeb, 0x27, 0x70, 0xd6, + 0x45, 0x25, 0xf8, 0xc1, 0xc0, 0xba, 0x41, 0x29, 0xe6, 0x92, 0x07, + 0x9a, 0x14, 0xff, 0x95, 0x6b, 0xe1, 0x0c, 0x2d, 0xa5, 0xd1, 0xc7, + 0xc1, 0xd9, 0x4f, 0x5e, 0x4c, 0xd7, 0x78, 0x5f, 0xb3, 0xb8, 0x05, + 0xa4, 0xf5, 0x01, 0x95, 0xdb, 0x08, 0xeb, 0x72, 0x5e, 0xf8, 0xc9, + 0xdd, 0x2b, 0x53, 0xc5, 0x2e, 0xe3, 0x77, 0x23, 0x1f, 0x82, 0x6d, + 0xea, 0x18, 0xba, 0xcb, 0x52, 0x78, 0x34, 0x8b, 0xa7, 0x69, 0x59, + 0x9c, 0x80, 0x51, 0x17, 0xdb, 0x46, 0x68, 0x40, 0xb8, 0x7b, 0x9e, + 0x43, 0xde, 0x0c, 0x1d, 0x60, 0xcf, 0x69, 0x9e, 0x46, 0xf1, 0xbc, + 0x29, 0xd1, 0x69, 0xf2, 0x08, 0x51, 0x0e, 0x31, 0x35, 0xdd, 0xc3, + 0xa1, 0x4c, 0x70, 0x7c, 0x1f, 0xe5, 0xbb, 0xf2, 0x05, 0xef, 0xb9, + 0x9a, 0x66, 0x63, 0x80, 0x09, 0x48, 0xc0, 0x6f, 0x52, 0xa2, 0x29, + 0xae, 0x0e, 0x01, 0xa6, 0xaa, 0x58, 0x0d, 0x3d, 0xdb, 0x73, 0x74, + 0x47, 0x83, 0x88, 0x12, 0x69, 0x7e, 0x05, 0x7f, 0xda, 0x10, 0xaf, + 0xbe, 0xc2, 0x17, 0xf0, 0x32, 0x4d, 0x56, 0xa0, 0xca, 0x16, 0xd2, + 0x8d, 0xbc, 0x98, 0x1c, 0xa6, 0x32, 0x5d, 0x83, 0xcb, 0x21, 0x60, + 0x54, 0x7b, 0xdb, 0x4a, 0x29, 0x36, 0x84, 0xcf, 0xb0, 0x6e, 0x88, + 0x6b, 0xb6, 0x7d, 0x34, 0x2a, 0xe8, 0x31, 0x5e, 0x35, 0xd1, 0xb5, + 0x1e, 0xf4, 0x65, 0x87, 0xd8, 0x69, 0xac, 0xda, 0xbe, 0xcd, 0xa3, + 0xd5, 0xba, 0xf8, 0x82, 0x21, 0xc5, 0x92, 0xd2, 0xb4, 0x45, 0x2c, + 0xbe, 0x0b, 0xc4, 0x32, 0xfe, 0x8f, 0x7c, 0x32, 0xb4, 0x82, 0xc6, + 0xea, 0xb6, 0x47, 0x0f, 0x8b, 0xef, 0x51, 0xc2, 0x83, 0xa7, 0xf9, + 0x5d, 0x41, 0xc8, 0x34, 0xdb, 0x0b, 0xa9, 0xc2, 0x18, 0x54, 0x8d, + 0xa6, 0x89, 0x03, 0xc5, 0x33, 0x00, 0x9a, 0x39, 0x2e, 0x83, 0x67, + 0x8f, 0xe6, 0xe8, 0x54, 0xaf, 0xbe, 0x2b, 0x80, 0x77, 0xfb, 0x9e, + 0x43, 0x7c, 0xa3, 0xdc, 0x42, 0x7e, 0x47, 0x33, 0x99, 0xea, 0x87, + 0x6f, 0x8e, 0xf3, 0xdc, 0x53, 0x8f, 0x8e, 0xd3, 0x84, 0x1e, 0x7d, + 0x9f, 0xc0, 0xd7, 0x4f, 0x76, 0xdb, 0x38, 0x0a, 0x45, 0x12, 0x28, + 0xab, 0x71, 0x41, 0x2d, 0x70, 0x89, 0x6d, 0x59, 0xa6, 0xa7, 0x10, + 0x27, 0xf4, 0x14, 0xd3, 0xf3, 0x2c, 0xc5, 0xf5, 0x20, 0x17, 0x81, + 0x06, 0x1e, 0x76, 0x03, 0xd5, 0xd0, 0x68, 0xe3, 0x96, 0x3a, 0x94, + 0xfe, 0x65, 0xc7, 0x6d, 0x16, 0xad, 0x22, 0x1e, 0x43, 0xdd, 0x9a, + 0xbc, 0xbe, 0x42, 0x65, 0x45, 0xfe, 0xfa, 0x17, 0x00, 0x0d, 0x3f, + 0x04, 0x19, 0x9b, 0xa9, 0x44, 0xd3, 0xe1, 0x49, 0x15, 0x60, 0xa2, + 0x2b, 0xa6, 0x66, 0x3b, 0x0a, 0x56, 0xe1, 0x34, 0xb8, 0xaa, 0xae, + 0x9b, 0x4c, 0xb7, 0x18, 0x0e, 0x82, 0x43, 0x6c, 0x15, 0x55, 0x05, + 0x76, 0x93, 0x12, 0x7a, 0x23, 0x2e, 0xff, 0x8a, 0x56, 0x09, 0xda, + 0x61, 0xf3, 0xdc, 0xfe, 0x7a, 0xa2, 0x9c, 0xf4, 0xd8, 0x79, 0x0c, + 0xfe, 0x90, 0xdd, 0xad, 0xf1, 0x43, 0xc5, 0x26, 0x6b, 0xc2, 0x5b, + 0xc6, 0x72, 0x5a, 0x1c, 0x67, 0x79, 0x3f, 0x0a, 0xbf, 0xf3, 0x2d, + 0x59, 0xd0, 0x30, 0x4d, 0x64, 0x58, 0x79, 0x53, 0x53, 0xb3, 0x46, + 0xbc, 0x1c, 0x18, 0x8f, 0x6c, 0xc5, 0xc4, 0x8e, 0xa1, 0xb8, 0x0e, + 0x34, 0xc7, 0x8e, 0x31, 0x31, 0x9d, 0xa9, 0xeb, 0x9a, 0x8e, 0x5d, + 0x0b, 0x7c, 0x8c, 0x01, 0x36, 0xd0, 0xe0, 0x7f, 0x49, 0x95, 0x76, + 0xa7 +}; + +const int standard_templates_data_len = 1860; + diff --git a/pandatool/src/xfile/standard_templates.cxx b/pandatool/src/xfile/standard_templates.cxx new file mode 100644 index 00000000..d9706bb9 --- /dev/null +++ b/pandatool/src/xfile/standard_templates.cxx @@ -0,0 +1,48 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file standard_templates.cxx + * @author drose + * @date 2004-10-04 + */ + +#include "standard_templates.h" + +// The binary data included here was generated from standardTemplates.x (in +// this directory) file via the utility program bin2c (defined in pandatool). +// It contains the set of template definitions that must be loaded before any +// standard template file can be properly interpreted. + +#ifndef CPPPARSER + +#if defined(HAVE_ZLIB) + +// If we have zlib available, we can store this file compressed, which is much +// smaller. + +// Regenerate this file with: + +// pcompress standardTemplates.x standardTemplates.x.pz bin2c -n +// standard_templates_data -o standardTemplates.x.pz.c standardTemplates.x.pz + +#include "standardTemplates.x.pz.c" + +#else // HAVE_ZLIB + +// If we don't have zlib, just include the whole uncompressed file. + +// Regenerate this file with: + +// bin2c -n standard_templates_data -o standardTemplates.x.c +// standardTemplates.x + +#include "standardTemplates.x.c" + +#endif // HAVE_ZLIB + +#endif // CPPPARSER diff --git a/pandatool/src/xfile/standard_templates.h b/pandatool/src/xfile/standard_templates.h new file mode 100644 index 00000000..b542f667 --- /dev/null +++ b/pandatool/src/xfile/standard_templates.h @@ -0,0 +1,26 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file standard_templates.h + * @author drose + * @date 2004-10-04 + */ + +#ifndef STANDARD_TEMPLATES_H +#define STANDARD_TEMPLATES_H + +#include "pandatoolbase.h" + +#ifndef CPPPARSER + +extern const unsigned char standard_templates_data[]; +extern const int standard_templates_data_len; + +#endif // CPPPARSER + +#endif diff --git a/pandatool/src/xfile/windowsGuid.I b/pandatool/src/xfile/windowsGuid.I new file mode 100644 index 00000000..5d0ce220 --- /dev/null +++ b/pandatool/src/xfile/windowsGuid.I @@ -0,0 +1,98 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file windowsGuid.I + * @author drose + * @date 2004-10-03 + */ + +/** + * + */ +INLINE WindowsGuid:: +WindowsGuid() { + memset(this, 0, sizeof(WindowsGuid)); +} + +/** + * + */ +INLINE WindowsGuid:: +WindowsGuid(unsigned long data1, + unsigned short data2, unsigned short data3, + unsigned char b1, unsigned char b2, unsigned char b3, + unsigned char b4, unsigned char b5, unsigned char b6, + unsigned char b7, unsigned char b8) : + _data1(data1), + _data2(data2), + _data3(data3), + _b1(b1), + _b2(b2), + _b3(b3), + _b4(b4), + _b5(b5), + _b6(b6), + _b7(b7), + _b8(b8) +{ +} + +/** + * + */ +INLINE WindowsGuid:: +WindowsGuid(const WindowsGuid ©) { + (*this) = copy; +} + +/** + * + */ +INLINE void WindowsGuid:: +operator = (const WindowsGuid ©) { + memcpy(this, ©, sizeof(WindowsGuid)); +} + +/** + * + */ +INLINE bool WindowsGuid:: +operator == (const WindowsGuid &other) const { + return compare_to(other) == 0; +} + +/** + * + */ +INLINE bool WindowsGuid:: +operator != (const WindowsGuid &other) const { + return compare_to(other) != 0; +} + +/** + * + */ +INLINE bool WindowsGuid:: +operator < (const WindowsGuid &other) const { + return compare_to(other) < 0; +} + +/** + * Returns a number less than zero if this WindowsGuid sorts before the other + * one, greater than zero if it sorts after, or zero if they are equivalent. + */ +INLINE int WindowsGuid:: +compare_to(const WindowsGuid &other) const { + return memcmp(this, &other, sizeof(WindowsGuid)); +} + +INLINE std::ostream & +operator << (std::ostream &out, const WindowsGuid &guid) { + guid.output(out); + return out; +} diff --git a/pandatool/src/xfile/windowsGuid.cxx b/pandatool/src/xfile/windowsGuid.cxx new file mode 100644 index 00000000..fbe64967 --- /dev/null +++ b/pandatool/src/xfile/windowsGuid.cxx @@ -0,0 +1,76 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file windowsGuid.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "windowsGuid.h" +#include "pnotify.h" + +#include // for sscanf, sprintf + +using std::string; + +/** + * Parses the hex representation in the indicated string and stores it in the + * WindowsGuid object. Returns true if successful, false if the string + * representation is malformed. + */ +bool WindowsGuid:: +parse_string(const string &str) { + unsigned long data1; + unsigned int data2, data3; + unsigned int b1, b2, b3, b4, b5, b6, b7, b8; + int result = sscanf(str.c_str(), + "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &data1, &data2, &data3, + &b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8); + if (result != 11) { + return false; + } + + _data1 = data1; + _data2 = data2; + _data3 = data3; + _b1 = b1; + _b2 = b2; + _b3 = b3; + _b4 = b4; + _b5 = b5; + _b6 = b6; + _b7 = b7; + _b8 = b8; + + return true; +} + +/** + * Returns a hex representation of the GUID. + */ +string WindowsGuid:: +format_string() const { + static const int buf_length = 128; // Actually, we only need 36 + 1 == 37. + char buffer[buf_length]; + sprintf(buffer, + "%08lx-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x", + _data1, _data2, _data3, + _b1, _b2, _b3, _b4, _b5, _b6, _b7, _b8); + nassertr((int)strlen(buffer) < buf_length, string()); + + return string(buffer); +} + +/** + * Outputs a hex representation of the GUID. + */ +void WindowsGuid:: +output(std::ostream &out) const { + out << format_string(); +} diff --git a/pandatool/src/xfile/windowsGuid.h b/pandatool/src/xfile/windowsGuid.h new file mode 100644 index 00000000..ea79d0be --- /dev/null +++ b/pandatool/src/xfile/windowsGuid.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file windowsGuid.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef WINDOWS_GUID_H +#define WINDOWS_GUID_H + +#include "pandatoolbase.h" + +#include // For memcpy, memcmp + +/** + * This is an implementation of the Windows GUID object, used everywhere as a + * world-unique identifier for anything and everything. In particular, it's + * used in the X file format to identify standard templates. + */ +class WindowsGuid { +public: + INLINE WindowsGuid(); + INLINE WindowsGuid(unsigned long data1, + unsigned short data2, unsigned short data3, + unsigned char b1, unsigned char b2, unsigned char b3, + unsigned char b4, unsigned char b5, unsigned char b6, + unsigned char b7, unsigned char b8); + INLINE WindowsGuid(const WindowsGuid ©); + INLINE void operator = (const WindowsGuid ©); + + INLINE bool operator == (const WindowsGuid &other) const; + INLINE bool operator != (const WindowsGuid &other) const; + INLINE bool operator < (const WindowsGuid &other) const; + INLINE int compare_to(const WindowsGuid &other) const; + + bool parse_string(const std::string &str); + std::string format_string() const; + + void output(std::ostream &out) const; + +private: + unsigned long _data1; + unsigned short _data2; + unsigned short _data3; + unsigned char _b1, _b2, _b3, _b4, _b5, _b6, _b7, _b8; +}; + +INLINE std::ostream &operator << (std::ostream &out, const WindowsGuid &guid); + +#include "windowsGuid.I" + +#endif diff --git a/pandatool/src/xfile/xFile.I b/pandatool/src/xfile/xFile.I new file mode 100644 index 00000000..cf223955 --- /dev/null +++ b/pandatool/src/xfile/xFile.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFile.I + * @author drose + * @date 2004-10-03 + */ diff --git a/pandatool/src/xfile/xFile.cxx b/pandatool/src/xfile/xFile.cxx new file mode 100644 index 00000000..3c0d7b34 --- /dev/null +++ b/pandatool/src/xfile/xFile.cxx @@ -0,0 +1,479 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFile.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFile.h" +#include "xParserDefs.h" +#include "xLexerDefs.h" +#include "xFileTemplate.h" +#include "xFileDataNodeTemplate.h" +#include "config_xfile.h" +#include "standard_templates.h" +#include "zStream.h" +#include "virtualFileSystem.h" +#include "dcast.h" + +using std::istream; +using std::istringstream; +using std::ostream; +using std::string; + +TypeHandle XFile::_type_handle; +PT(XFile) XFile::_standard_templates; + +/** + * + */ +XFile:: +XFile(bool keep_names) : XFileNode(this) { + _major_version = 3; + _minor_version = 2; + _format_type = FT_text; + _float_size = FS_64; + _keep_names = keep_names; +} + +/** + * + */ +XFile:: +~XFile() { + clear(); +} + +/** + * Removes all of the classes defined within the XFile and prepares it for + * reading a new file. + */ +void XFile:: +clear() { + XFileNode::clear(); + + _nodes_by_guid.clear(); +} + +/** + * Opens and reads the indicated .x file by name. The nodes and templates + * defined in the file will be appended to the set of nodes already recorded, + * if any. + * + * Returns true if the file is successfully read, false if there was an error + * (in which case the file might have been partially read). + */ +bool XFile:: +read(Filename filename) { + filename.set_text(); + VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); + istream *in = vfs->open_read_file(filename, true); + if (in == nullptr) { + xfile_cat.error() + << "Cannot open " << filename << " for reading.\n"; + return false; + } + bool okflag = read(*in, filename); + vfs->close_read_file(in); + return okflag; +} + +/** + * Parses the already-opened input stream for distributed class descriptions. + * The filename parameter is optional and is only used when reporting errors. + * + * The distributed classes defined in the file will be appended to the set of + * distributed classes already recorded, if any. + * + * Returns true if the file is successfully read, false if there was an error + * (in which case the file might have been partially read). + */ +bool XFile:: +read(istream &in, const string &filename) { + if (!read_header(in)) { + return false; + } + + if (_format_type != FT_text) { + // Does anyone actually use the binary format? It wouldn't be too hard to + // support it if there were any reason at all to do so. + xfile_cat.error() + << "Cannot read binary .x files at this time.\n"; + return false; + } + + // We must call this first so the standard templates file will be parsed and + // available by the time we need it--it's tricky to invoke the parser from + // within another parser instance. + get_standard_templates(); + + x_init_parser(in, filename, *this); + xyyparse(); + x_cleanup_parser(); + + return (x_error_count() == 0); +} + +/** + * Opens the indicated filename for output and writes a parseable description + * of all the known distributed classes to the file. + * + * Returns true if the description is successfully written, false otherwise. + */ +bool XFile:: +write(Filename filename) const { + std::ofstream out; + + // We actually open the file to write in binary mode, to avoid the MS-DOS + // newline characters (since Windows seems to do this too). + filename.set_binary(); + filename.open_write(out); + + if (!out) { + xfile_cat.error() + << "Can't open " << filename << " for output.\n"; + return false; + } + +#ifdef HAVE_ZLIB + if (filename.get_extension() == "pz") { + // The filename ends in .pz, which means to automatically compress the X + // file that we write. + OCompressStream compressor(&out, false); + return write(compressor); + } +#endif // HAVE_ZLIB + + return write(out); +} + +/** + * Writes a parseable description of all the known nodes and templates to the + * stream. + * + * Returns true if the description is successfully written, false otherwise. + */ +bool XFile:: +write(ostream &out) const { + if (!write_header(out)) { + return false; + } + + write_text(out, 0); + + return true; +} + +/** + * Returns the template associated with the indicated name, if any, or NULL if + * none. + */ +XFileTemplate *XFile:: +find_template(const string &name) const { + XFileTemplate *standard = nullptr; + const XFile *standard_templates = get_standard_templates(); + if (standard_templates != this) { + standard = standard_templates->find_template(name); + } + + XFileNode *child = find_child(name); + if (child != nullptr && + child->is_of_type(XFileTemplate::get_class_type())) { + XFileTemplate *xtemplate = DCAST(XFileTemplate, child); + if (standard != nullptr && xtemplate->matches(standard)) { + // If the template matches a standard template, return the standard + // instead. The assumption is that code may expect a certain naming + // scheme for the data elements of the standard template, so we want to + // be sure to provide it. + return standard; + } + return xtemplate; + } + + return standard; +} + +/** + * Returns the template associated with the indicated GUID, if any, or NULL if + * none. + */ +XFileTemplate *XFile:: +find_template(const WindowsGuid &guid) const { + XFileTemplate *standard = nullptr; + const XFile *standard_templates = get_standard_templates(); + if (standard_templates != this) { + standard = standard_templates->find_template(guid); + } + + NodesByGuid::const_iterator gi; + gi = _nodes_by_guid.find(guid); + if (gi != _nodes_by_guid.end() && + (*gi).second->is_of_type(XFileTemplate::get_class_type())) { + XFileTemplate *xtemplate = DCAST(XFileTemplate, (*gi).second); + if (standard != nullptr && xtemplate->matches(standard)) { + // If the template matches a standard template, return the standard + // instead. The assumption is that code may expect a certain naming + // scheme for the data elements of the standard template, so we want to + // be sure to provide it. + return standard; + } + return xtemplate; + } + + return standard; +} + +/** + * Returns the standard template associated with the indicated name, if any, + * or NULL if none. + */ +XFileTemplate *XFile:: +find_standard_template(const string &name) { + const XFile *standard_templates = get_standard_templates(); + return standard_templates->find_template(name); +} + +/** + * Returns the template associated with the indicated GUID, if any, or NULL if + * none. + */ +XFileTemplate *XFile:: +find_standard_template(const WindowsGuid &guid) { + const XFile *standard_templates = get_standard_templates(); + return standard_templates->find_template(guid); +} + +/** + * Returns the data object associated with the indicated name, if any, or NULL + * if none. + */ +XFileDataNodeTemplate *XFile:: +find_data_object(const string &name) const { + XFileNode *child = find_descendent(name); + if (child != nullptr && + child->is_of_type(XFileDataNodeTemplate::get_class_type())) { + return DCAST(XFileDataNodeTemplate, child); + } + + return nullptr; +} + +/** + * Returns the data object associated with the indicated GUID, if any, or NULL + * if none. + */ +XFileDataNodeTemplate *XFile:: +find_data_object(const WindowsGuid &guid) const { + NodesByGuid::const_iterator gi; + gi = _nodes_by_guid.find(guid); + if (gi != _nodes_by_guid.end() && + (*gi).second->is_of_type(XFileDataNodeTemplate::get_class_type())) { + return DCAST(XFileDataNodeTemplate, (*gi).second); + } + + return nullptr; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFile:: +write_text(ostream &out, int indent_level) const { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write_text(out, indent_level); + out << "\n"; + } +} + +/** + * Reads the header and magic number associated with the file. Returns true + * on success, false otherwise. + */ +bool XFile:: +read_header(istream &in) { + char magic[4]; + if (!in.read(magic, 4)) { + xfile_cat.error() + << "Empty file.\n"; + return false; + } + + if (memcmp(magic, "xof ", 4) != 0) { + xfile_cat.error() + << "Not a DirectX file.\n"; + return false; + } + + char version[4]; + if (!in.read(version, 4)) { + xfile_cat.error() + << "Truncated file.\n"; + return false; + } + _major_version = (version[0] - '0') * 10 + (version[1] - '0'); + _minor_version = (version[2] - '0') * 10 + (version[3] - '0'); + + char format[4]; + if (!in.read(format, 4)) { + xfile_cat.error() + << "Truncated file.\n"; + return false; + } + + if (memcmp(format, "txt ", 4) == 0) { + _format_type = FT_text; + + } else if (memcmp(format, "bin ", 4) == 0) { + _format_type = FT_binary; + + } else if (memcmp(format, "com ", 4) == 0) { + _format_type = FT_compressed; + + } else { + xfile_cat.error() + << "Unknown format type: " << string(format, 4) << "\n"; + return false; + } + + if (_format_type == FT_compressed) { + // Read and ignore the compression type, since we don't support + // compression anyway. + char compression_type[4]; + in.read(compression_type, 4); + } + + char float_size[4]; + if (!in.read(float_size, 4)) { + xfile_cat.error() + << "Truncated file.\n"; + return false; + } + + if (memcmp(float_size, "0032", 4) == 0) { + _float_size = FS_32; + + } else if (memcmp(float_size, "0064", 4) == 0) { + _float_size = FS_64; + + } else { + xfile_cat.error() + << "Unknown float size: " << string(float_size, 4) << "\n"; + return false; + } + + return true; +} + +/** + * Writes the header and magic number associated with the file. Returns true + * on success, false otherwise. + */ +bool XFile:: +write_header(ostream &out) const { + out.write("xof ", 4); + + char buffer[128]; + sprintf(buffer, "%02d%02d", _major_version, _minor_version); + if (strlen(buffer) != 4) { + xfile_cat.error() + << "Invalid version: " << _major_version << "." << _minor_version + << "\n"; + return false; + } + + out.write(buffer, 4); + + switch (_format_type) { + case FT_text: + out.write("txt ", 4); + break; + + case FT_binary: + out.write("bin ", 4); + break; + + case FT_compressed: + out.write("cmp ", 4); + break; + + default: + xfile_cat.error() + << "Invalid format type: " << _format_type << "\n"; + return false; + } + + if (_format_type == FT_compressed) { + // Write a bogus compression type, just so we have a valid header. + out.write("xxx ", 4); + } + + switch (_float_size) { + case FS_32: + out.write("0032", 4); + break; + + case FS_64: + out.write("0064", 4); + break; + + default: + xfile_cat.error() + << "Invalid float size: " << _float_size << "\n"; + return false; + } + + if (_format_type == FT_text) { + // If it's a text format, we can now write a newline. + out << "\n"; + } + + return true; +} + +/** + * Returns a global XFile object that contains the standard list of Direct3D + * template definitions that may be assumed to be at the head of every file. + */ +const XFile *XFile:: +get_standard_templates() { + if (_standard_templates == nullptr) { + // The standardTemplates.x file has been compiled into this binary. + // Extract it out. + + string data((const char *)standard_templates_data, standard_templates_data_len); + +#ifdef HAVE_ZLIB + // The data is stored compressed; decompress it on-the-fly. + istringstream inz(data); + IDecompressStream in(&inz, false); + +#else + // The data is stored uncompressed, so just load it. + istringstream in(data); +#endif // HAVE_ZLIB + + _standard_templates = new XFile; + if (!_standard_templates->read(in, "standardTemplates.x")) { + xfile_cat.error() + << "Internal error: Unable to parse built-in standardTemplates.x!\n"; + } + + // Now flag all of these templates as "standard". + for (int i = 0; i < _standard_templates->get_num_children(); i++) { + XFileNode *child = _standard_templates->get_child(i); + if (child->is_of_type(XFileTemplate::get_class_type())) { + XFileTemplate *xtemplate = DCAST(XFileTemplate, child); + xtemplate->_is_standard = true; + } + } + } + + return _standard_templates; +} diff --git a/pandatool/src/xfile/xFile.h b/pandatool/src/xfile/xFile.h new file mode 100644 index 00000000..5dacb3c4 --- /dev/null +++ b/pandatool/src/xfile/xFile.h @@ -0,0 +1,104 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFile.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILE_H +#define XFILE_H + +#include "pandatoolbase.h" +#include "xFileNode.h" +#include "xFileDataNode.h" +#include "windowsGuid.h" +#include "filename.h" +#include "pmap.h" +#include "pointerTo.h" + +class XFileTemplate; +class XFileDataNodeTemplate; + +/** + * This represents the complete contents of an X file (file.x) in memory. It + * may be read or written from or to a disk file. + */ +class XFile : public XFileNode { +public: + XFile(bool keep_names=false); + ~XFile(); + + virtual void clear() final; + + bool read(Filename filename); + bool read(std::istream &in, const std::string &filename = std::string()); + + bool write(Filename filename) const; + bool write(std::ostream &out) const; + + XFileTemplate *find_template(const std::string &name) const; + XFileTemplate *find_template(const WindowsGuid &guid) const; + + static XFileTemplate *find_standard_template(const std::string &name); + static XFileTemplate *find_standard_template(const WindowsGuid &guid); + + XFileDataNodeTemplate *find_data_object(const std::string &name) const; + XFileDataNodeTemplate *find_data_object(const WindowsGuid &guid) const; + + virtual void write_text(std::ostream &out, int indent_level) const; + + enum FormatType { + FT_text, + FT_binary, + FT_compressed, + }; + enum FloatSize { + FS_32, + FS_64, + }; + +private: + bool read_header(std::istream &in); + bool write_header(std::ostream &out) const; + + static const XFile *get_standard_templates(); + + int _major_version, _minor_version; + FormatType _format_type; + FloatSize _float_size; + bool _keep_names; + + typedef pmap NodesByGuid; + NodesByGuid _nodes_by_guid; + + static PT(XFile) _standard_templates; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileNode::init_type(); + register_type(_type_handle, "XFile", + XFileNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class XFileNode; +}; + +#include "xFile.I" + +#endif diff --git a/pandatool/src/xfile/xFileArrayDef.I b/pandatool/src/xfile/xFileArrayDef.I new file mode 100644 index 00000000..f231f0c6 --- /dev/null +++ b/pandatool/src/xfile/xFileArrayDef.I @@ -0,0 +1,60 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileArrayDef.I + * @author drose + * @date 2004-10-03 + */ + +/** + * + */ +INLINE XFileArrayDef:: +XFileArrayDef(int fixed_size) : + _fixed_size(fixed_size), + _dynamic_size(nullptr) +{ +} + +/** + * + */ +INLINE XFileArrayDef:: +XFileArrayDef(XFileDataDef *dynamic_size) : + _fixed_size(0), + _dynamic_size(dynamic_size) +{ +} + +/** + * Returns true if this array definition specifies a const-size array, false + * if it is a dynamic-size array. + */ +INLINE bool XFileArrayDef:: +is_fixed_size() const { + return (_dynamic_size == nullptr); +} + +/** + * Returns the const size of the array, if is_fixed_size() returned true. + */ +INLINE int XFileArrayDef:: +get_fixed_size() const { + nassertr(is_fixed_size(), 0); + return _fixed_size; +} + +/** + * Returns the data element that names the dynamic size of the array, if + * is_fixed_size() returned false. + */ +INLINE XFileDataDef *XFileArrayDef:: +get_dynamic_size() const { + nassertr(!is_fixed_size(), nullptr); + return _dynamic_size; +} diff --git a/pandatool/src/xfile/xFileArrayDef.cxx b/pandatool/src/xfile/xFileArrayDef.cxx new file mode 100644 index 00000000..91078f55 --- /dev/null +++ b/pandatool/src/xfile/xFileArrayDef.cxx @@ -0,0 +1,75 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileArrayDef.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileArrayDef.h" +#include "xFileDataDef.h" +#include "xFileDataObject.h" + +/** + * Returns the size of the array dimension. If this is a fixed array, the + * size is trivial; if it is dynamic, the size is determined by looking up the + * dynamic_size element in the prev_data table (which lists all of the data + * values already defined at this scoping level). + */ +int XFileArrayDef:: +get_size(const XFileNode::PrevData &prev_data) const { + if (is_fixed_size()) { + return _fixed_size; + } else { + XFileNode::PrevData::const_iterator pi; + pi = prev_data.find(_dynamic_size); + nassertr_always(pi != prev_data.end(), 0); + nassertr((*pi).second != nullptr, 0); + return (*pi).second->i(); + } +} + +/** + * + */ +void XFileArrayDef:: +output(std::ostream &out) const { + if (is_fixed_size()) { + out << "[" << _fixed_size << "]"; + } else { + out << "[" << _dynamic_size->get_name() << "]"; + } +} + +/** + * Returns true if the node, particularly a template node, is structurally + * equivalent to the other node (which must be of the same type). This checks + * data element types, but does not compare data element names. + */ +bool XFileArrayDef:: +matches(const XFileArrayDef &other, const XFileDataDef *parent, + const XFileDataDef *other_parent) const { + if (other.is_fixed_size() != is_fixed_size()) { + return false; + } + if (is_fixed_size()) { + if (other.get_fixed_size() != get_fixed_size()) { + return false; + } + + } else { + int child_index = parent->find_child_index(get_dynamic_size()); + int other_child_index = + other_parent->find_child_index(other.get_dynamic_size()); + if (other_child_index != child_index) { + return false; + } + } + + return true; +} diff --git a/pandatool/src/xfile/xFileArrayDef.h b/pandatool/src/xfile/xFileArrayDef.h new file mode 100644 index 00000000..5871e6e2 --- /dev/null +++ b/pandatool/src/xfile/xFileArrayDef.h @@ -0,0 +1,49 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileArrayDef.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILEARRAYDEF_H +#define XFILEARRAYDEF_H + +#include "pandatoolbase.h" +#include "pnotify.h" +#include "xFileNode.h" + +class XFileDataDef; + +/** + * Defines one level of array bounds for an associated XFileDataDef element. + */ +class XFileArrayDef { +public: + INLINE XFileArrayDef(int fixed_size); + INLINE XFileArrayDef(XFileDataDef *dynamic_size); + + INLINE bool is_fixed_size() const; + INLINE int get_fixed_size() const; + INLINE XFileDataDef *get_dynamic_size() const; + + int get_size(const XFileNode::PrevData &prev_data) const; + + void output(std::ostream &out) const; + + bool matches(const XFileArrayDef &other, const XFileDataDef *parent, + const XFileDataDef *other_parent) const; + +private: + int _fixed_size; + XFileDataDef *_dynamic_size; +}; + +#include "xFileArrayDef.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataDef.I b/pandatool/src/xfile/xFileDataDef.I new file mode 100644 index 00000000..65a7d9c3 --- /dev/null +++ b/pandatool/src/xfile/xFileDataDef.I @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataDef.I + * @author drose + * @date 2004-10-03 + */ + +/** + * + */ +INLINE XFileDataDef:: +XFileDataDef(XFile *x_file, const std::string &name, + XFileDataDef::Type type, XFileTemplate *xtemplate) : + XFileNode(x_file, name), + _type(type), + _template(xtemplate) +{ +} + +/** + * Returns the primitive type of this element, or T_template if this + * represents a nested template object. + */ +INLINE XFileDataDef::Type XFileDataDef:: +get_data_type() const { + return _type; +} + +/** + * If get_data_type() returned T_template, this returns the particular + * template pointer that this object represents. + */ +INLINE XFileTemplate *XFileDataDef:: +get_template() const { + return _template; +} + +/** + * Returns the number of dimensions of array elements on this data object, or + * 0 if the data object is not an array. + */ +INLINE int XFileDataDef:: +get_num_array_defs() const { + return _array_def.size(); +} + +/** + * Returns the description of the nth dimension of array elements on this data + * object. + */ +INLINE const XFileArrayDef &XFileDataDef:: +get_array_def(int i) const { + nassertr(i >= 0 && i < (int)_array_def.size(), _array_def[0]); + return _array_def[i]; +} diff --git a/pandatool/src/xfile/xFileDataDef.cxx b/pandatool/src/xfile/xFileDataDef.cxx new file mode 100644 index 00000000..5fe98389 --- /dev/null +++ b/pandatool/src/xfile/xFileDataDef.cxx @@ -0,0 +1,496 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataDef.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileDataDef.h" +#include "indent.h" +#include "xLexerDefs.h" +#include "xFileParseData.h" +#include "xFileDataObjectInteger.h" +#include "xFileDataObjectDouble.h" +#include "xFileDataObjectString.h" +#include "xFileDataNodeTemplate.h" +#include "xFileDataObjectArray.h" +#include "string_utils.h" + +TypeHandle XFileDataDef::_type_handle; + +/** + * + */ +XFileDataDef:: +~XFileDataDef() { + clear(); +} + +/** + * + */ +void XFileDataDef:: +clear() { + XFileNode::clear(); + _array_def.clear(); +} + +/** + * Adds an additional array dimension to the data description. + */ +void XFileDataDef:: +add_array_def(const XFileArrayDef &array_def) { + _array_def.push_back(array_def); +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataDef:: +write_text(std::ostream &out, int indent_level) const { + indent(out, indent_level); + + if (!_array_def.empty()) { + out << "array "; + } + + switch (_type) { + case T_word: + out << "WORD"; + break; + + case T_dword: + out << "DWORD"; + break; + + case T_float: + out << "FLOAT"; + break; + + case T_double: + out << "DOUBLE"; + break; + + case T_char: + out << "CHAR"; + break; + + case T_uchar: + out << "UCHAR"; + break; + + case T_sword: + out << "SWORD"; + break; + + case T_sdword: + out << "SDWORD"; + break; + + case T_string: + out << "STRING"; + break; + + case T_cstring: + out << "CSTRING"; + break; + + case T_unicode: + out << "UNICODE"; + break; + + case T_template: + out << _template->get_name(); + break; + } + + if (has_name()) { + out << " " << get_name(); + } + + ArrayDef::const_iterator ai; + for (ai = _array_def.begin(); ai != _array_def.end(); ++ai) { + (*ai).output(out); + } + + out << ";\n"; +} + +/** + * This is called on the template that defines an object, once the data for + * the object has been parsed. It is responsible for identifying which + * component of the template owns each data element, and packing the data + * elements appropriately back into the object. + * + * It returns true on success, or false on an error (e.g. not enough data + * elements, mismatched data type). + */ +bool XFileDataDef:: +repack_data(XFileDataObject *object, + const XFileParseDataList &parse_data_list, + XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + // We'll fill this in with the data value we pack, if any. + PT(XFileDataObject) data_value; + + // What kind of data element are we expecting? + switch (_type) { + case T_word: + case T_dword: + case T_char: + case T_uchar: + case T_sword: + case T_sdword: + // Expected integer data. + data_value = unpack_value(parse_data_list, 0, + prev_data, index, sub_index, + &XFileDataDef::unpack_integer_value); + break; + + case T_float: + case T_double: + data_value = unpack_value(parse_data_list, 0, + prev_data, index, sub_index, + &XFileDataDef::unpack_double_value); + break; + + case T_string: + case T_cstring: + case T_unicode: + data_value = unpack_value(parse_data_list, 0, + prev_data, index, sub_index, + &XFileDataDef::unpack_string_value); + break; + + case T_template: + data_value = unpack_value(parse_data_list, 0, + prev_data, index, sub_index, + &XFileDataDef::unpack_template_value); + break; + } + + if (data_value != nullptr) { + object->add_element(data_value); + prev_data[this] = data_value; + } + + return XFileNode::repack_data(object, parse_data_list, + prev_data, index, sub_index); +} + +/** + * This is similar to repack_data(), except it is used to fill the initial + * values for a newly-created template object to zero. + */ +bool XFileDataDef:: +fill_zero_data(XFileDataObject *object) const { + PT(XFileDataObject) data_value; + + // What kind of data element are we expecting? + switch (_type) { + case T_word: + case T_dword: + case T_char: + case T_uchar: + case T_sword: + case T_sdword: + data_value = zero_fill_value(0, &XFileDataDef::zero_fill_integer_value); + break; + + case T_float: + case T_double: + data_value = zero_fill_value(0, &XFileDataDef::zero_fill_double_value); + break; + + case T_string: + case T_cstring: + case T_unicode: + data_value = zero_fill_value(0, &XFileDataDef::zero_fill_string_value); + break; + + case T_template: + data_value = zero_fill_value(0, &XFileDataDef::zero_fill_template_value); + break; + } + + if (data_value != nullptr) { + object->add_element(data_value); + } + + return XFileNode::fill_zero_data(object); +} + +/** + * Returns true if the node, particularly a template node, is structurally + * equivalent to the other node (which must be of the same type). This checks + * data element types, but does not compare data element names. + */ +bool XFileDataDef:: +matches(const XFileNode *other) const { + if (!XFileNode::matches(other)) { + return false; + } + + const XFileDataDef *data_def = DCAST(XFileDataDef, other); + if (data_def->get_data_type() != get_data_type()) { + return false; + } + + if (get_data_type() == T_template && + !get_template()->matches(data_def->get_template())) { + return false; + } + + if (data_def->get_num_array_defs() != get_num_array_defs()) { + return false; + } + + for (int i = 0; i < get_num_array_defs(); i++) { + if (!get_array_def(i).matches(data_def->get_array_def(i), + this, data_def)) { + return false; + } + } + + return true; +} + + +/** + * Unpacks and returns the next sequential integer value from the + * parse_data_list. + */ +PT(XFileDataObject) XFileDataDef:: +unpack_integer_value(const XFileParseDataList &parse_data_list, + const XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + nassertr(index < parse_data_list._list.size(), nullptr); + const XFileParseData &parse_data = parse_data_list._list[index]; + + PT(XFileDataObject) data_value; + + if ((parse_data._parse_flags & XFileParseData::PF_int) != 0) { + nassertr(sub_index < parse_data._int_list.size(), nullptr); + int value = parse_data._int_list[sub_index]; + data_value = new XFileDataObjectInteger(this, value); + + sub_index++; + if (sub_index >= parse_data._int_list.size()) { + index++; + sub_index = 0; + } + + } else { + parse_data.yyerror("Expected integer data for " + get_name()); + } + + return data_value; +} + +/** + * Unpacks and returns the next sequential double value from the + * parse_data_list. + */ +PT(XFileDataObject) XFileDataDef:: +unpack_double_value(const XFileParseDataList &parse_data_list, + const XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + nassertr(index < parse_data_list._list.size(), nullptr); + const XFileParseData &parse_data = parse_data_list._list[index]; + + PT(XFileDataObject) data_value; + + if ((parse_data._parse_flags & XFileParseData::PF_double) != 0) { + nassertr(sub_index < parse_data._double_list.size(), nullptr); + double value = parse_data._double_list[sub_index]; + data_value = new XFileDataObjectDouble(this, value); + + sub_index++; + if (sub_index >= parse_data._double_list.size()) { + index++; + sub_index = 0; + } + + } else if ((parse_data._parse_flags & XFileParseData::PF_int) != 0) { + nassertr(sub_index < parse_data._int_list.size(), nullptr); + int value = parse_data._int_list[sub_index]; + data_value = new XFileDataObjectDouble(this, value); + + sub_index++; + if (sub_index >= parse_data._int_list.size()) { + index++; + sub_index = 0; + } + + } else { + parse_data.yyerror("Expected floating-point data for " + get_name()); + } + + return data_value; +} + +/** + * Unpacks and returns the next sequential string value from the + * parse_data_list. + */ +PT(XFileDataObject) XFileDataDef:: +unpack_string_value(const XFileParseDataList &parse_data_list, + const XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + nassertr(index < parse_data_list._list.size(), nullptr); + const XFileParseData &parse_data = parse_data_list._list[index]; + + PT(XFileDataObject) data_value; + + if ((parse_data._parse_flags & XFileParseData::PF_string) != 0) { + data_value = new XFileDataObjectString(this, parse_data._string); + index++; + sub_index = 0; + + } else { + parse_data.yyerror("Expected string data for " + get_name()); + } + + return data_value; +} + +/** + * Unpacks a nested template object's data. + */ +PT(XFileDataObject) XFileDataDef:: +unpack_template_value(const XFileParseDataList &parse_data_list, + const XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + PT(XFileDataNodeTemplate) data_value = + new XFileDataNodeTemplate(get_x_file(), get_name(), _template); + + PrevData nested_prev_data(prev_data); + if (!_template->repack_data(data_value, parse_data_list, + nested_prev_data, index, sub_index)) { + return nullptr; + } + + return data_value; +} + +/** + * Unpacks and returns the next sequential value, of the type supported by the + * unpack_method. If the value is an array type, unpacks all the elements of + * the array. + */ +PT(XFileDataObject) XFileDataDef:: +unpack_value(const XFileParseDataList &parse_data_list, int array_index, + const XFileDataDef::PrevData &prev_data, + size_t &index, size_t &sub_index, + XFileDataDef::UnpackMethod unpack_method) const { + PT(XFileDataObject) data_value; + + if (array_index == (int)_array_def.size()) { + if (index >= parse_data_list._list.size()) { + xyyerror("Not enough data elements in structure at " + get_name()); + return nullptr; + } + data_value = (this->*unpack_method)(parse_data_list, prev_data, + index, sub_index); + + } else { + data_value = new XFileDataObjectArray(this); + int array_size = _array_def[array_index].get_size(prev_data); + + for (int i = 0; i < array_size; i++) { + if (index >= parse_data_list._list.size()) { + xyyerror(std::string("Expected ") + format_string(array_size) + + " array elements, found " + format_string(i)); + return data_value; + } + + PT(XFileDataObject) array_element = + unpack_value(parse_data_list, array_index + 1, + prev_data, index, sub_index, + unpack_method); + if (array_element == nullptr) { + return data_value; + } + data_value->add_element(array_element); + } + } + + return data_value; +} + +/** + * Returns a newly-allocated zero integer value. + */ +PT(XFileDataObject) XFileDataDef:: +zero_fill_integer_value() const { + return new XFileDataObjectInteger(this, 0); +} + +/** + * Returns a newly-allocated zero floating-point value. + */ +PT(XFileDataObject) XFileDataDef:: +zero_fill_double_value() const { + return new XFileDataObjectDouble(this, 0.0); +} + +/** + * Returns a newly-allocated empty string value. + */ +PT(XFileDataObject) XFileDataDef:: +zero_fill_string_value() const { + return new XFileDataObjectString(this, ""); +} + +/** + * Returns a newly-allocated zero-filled nested template value. + */ +PT(XFileDataObject) XFileDataDef:: +zero_fill_template_value() const { + PT(XFileDataObject) data_value = + new XFileDataNodeTemplate(get_x_file(), get_name(), _template); + if (!_template->fill_zero_data(data_value)) { + return nullptr; + } + + return data_value; +} + +/** + * Creates a zero-valued element for the next sequential value, of the type + * returned by the zero_fill_method. If the value is a fixed-size array type, + * zero-fills all the elements of the array. + */ +PT(XFileDataObject) XFileDataDef:: +zero_fill_value(int array_index, + XFileDataDef::ZeroFillMethod zero_fill_method) const { + PT(XFileDataObject) data_value; + + if (array_index == (int)_array_def.size()) { + data_value = (this->*zero_fill_method)(); + + } else { + data_value = new XFileDataObjectArray(this); + int array_size = 0; + if (_array_def[array_index].is_fixed_size()) { + array_size = _array_def[array_index].get_fixed_size(); + } + + for (int i = 0; i < array_size; i++) { + PT(XFileDataObject) array_element = + zero_fill_value(array_index + 1, zero_fill_method); + if (array_element == nullptr) { + return nullptr; + } + data_value->add_element(array_element); + } + } + + return data_value; +} diff --git a/pandatool/src/xfile/xFileDataDef.h b/pandatool/src/xfile/xFileDataDef.h new file mode 100644 index 00000000..adcac50d --- /dev/null +++ b/pandatool/src/xfile/xFileDataDef.h @@ -0,0 +1,137 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataDef.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILEDATADEF_H +#define XFILEDATADEF_H + +#include "pandatoolbase.h" +#include "namable.h" +#include "xFileNode.h" +#include "xFileArrayDef.h" +#include "xFileTemplate.h" +#include "xFileDataObject.h" +#include "pvector.h" +#include "pointerTo.h" + +/** + * A definition of a single data element appearing within a template record. + * This class represents the *definition* of the data element (e.g. DWORD + * nVertices); see XFileDataObject for its *value* (e.g. 12). + */ +class XFileDataDef : public XFileNode { +public: + enum Type { + T_word, + T_dword, + T_float, + T_double, + T_char, + T_uchar, + T_sword, + T_sdword, + T_string, + T_cstring, + T_unicode, + T_template, + }; + + INLINE XFileDataDef(XFile *x_file, const std::string &name, + Type type, XFileTemplate *xtemplate = nullptr); + virtual ~XFileDataDef(); + + virtual void clear() final; + void add_array_def(const XFileArrayDef &array_def); + + INLINE Type get_data_type() const; + INLINE XFileTemplate *get_template() const; + + INLINE int get_num_array_defs() const; + INLINE const XFileArrayDef &get_array_def(int i) const; + + virtual void write_text(std::ostream &out, int indent_level) const; + + virtual bool repack_data(XFileDataObject *object, + const XFileParseDataList &parse_data_list, + PrevData &prev_data, + size_t &index, size_t &sub_index) const; + + virtual bool fill_zero_data(XFileDataObject *object) const; + + virtual bool matches(const XFileNode *other) const; + +private: + typedef PT(XFileDataObject) + (XFileDataDef::*UnpackMethod)(const XFileParseDataList &parse_data_list, + const PrevData &prev_data, + size_t &index, size_t &sub_index) const; + typedef PT(XFileDataObject) + (XFileDataDef::*ZeroFillMethod)() const; + + PT(XFileDataObject) + unpack_integer_value(const XFileParseDataList &parse_data_list, + const PrevData &prev_data, + size_t &index, size_t &sub_index) const; + PT(XFileDataObject) + unpack_double_value(const XFileParseDataList &parse_data_list, + const PrevData &prev_data, + size_t &index, size_t &sub_index) const; + PT(XFileDataObject) + unpack_string_value(const XFileParseDataList &parse_data_list, + const PrevData &prev_data, + size_t &index, size_t &sub_index) const; + PT(XFileDataObject) + unpack_template_value(const XFileParseDataList &parse_data_list, + const PrevData &prev_data, + size_t &index, size_t &sub_index) const; + + PT(XFileDataObject) + unpack_value(const XFileParseDataList &parse_data_list, int array_index, + const PrevData &prev_data, + size_t &index, size_t &sub_index, + UnpackMethod unpack_method) const; + + PT(XFileDataObject) zero_fill_integer_value() const; + PT(XFileDataObject) zero_fill_double_value() const; + PT(XFileDataObject) zero_fill_string_value() const; + PT(XFileDataObject) zero_fill_template_value() const; + PT(XFileDataObject) + zero_fill_value(int array_index, ZeroFillMethod zero_fill_method) const; + +private: + Type _type; + PT(XFileTemplate) _template; + + typedef pvector ArrayDef; + ArrayDef _array_def; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileNode::init_type(); + register_type(_type_handle, "XFileDataDef", + XFileNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataDef.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataNode.I b/pandatool/src/xfile/xFileDataNode.I new file mode 100644 index 00000000..5591778e --- /dev/null +++ b/pandatool/src/xfile/xFileDataNode.I @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNode.I + * @author drose + * @date 2004-10-08 + */ + +/** + * Since the children of an XFileDataNode are syntactically constrained to + * themselves be XFileDataNodes, this is a convenience function that returns + * the same thing as XFileNode::get_child(), but it is cast to a type + * XFileDataNode and dereferenced. + */ +INLINE const XFileDataNode &XFileDataNode:: +get_data_child(int n) const { + return *DCAST(XFileDataNode, get_child(n)); +} + + +/** + * Returns the template used to define this data object. Since the only + * classes to inherit from XFileDataNode are XFileDataNodeTemplate and + * XFileDataNodeReference, both of which represent a class that is defined by + * a template, it makes sense to put this common method here in the base + * class. + */ +INLINE XFileTemplate *XFileDataNode:: +get_template() const { + return _template; +} + +/** + * A convenience function to return the name of the template used to define + * this data object. + */ +INLINE const std::string &XFileDataNode:: +get_template_name() const { + return _template->get_name(); +} diff --git a/pandatool/src/xfile/xFileDataNode.cxx b/pandatool/src/xfile/xFileDataNode.cxx new file mode 100644 index 00000000..bc132669 --- /dev/null +++ b/pandatool/src/xfile/xFileDataNode.cxx @@ -0,0 +1,65 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNode.cxx + * @author drose + * @date 2004-10-08 + */ + +#include "xFileDataNode.h" +#include "indent.h" + +TypeHandle XFileDataNode::_type_handle; + +/** + * + */ +XFileDataNode:: +XFileDataNode(XFile *x_file, const std::string &name, + XFileTemplate *xtemplate) : + XFileNode(x_file, name), + _template(xtemplate) +{ +} + +/** + * Returns true if this node represents a data object that is the instance of + * some template, or false otherwise. This also returns true for references + * to objects (which are generally treated just like the objects themselves). + * + * If this returns true, the node must be of type XFileDataNode (it is either + * an XFileDataNodeTemplate or an XFileDataNodeReference). + */ +bool XFileDataNode:: +is_object() const { + return true; +} + +/** + * Returns true if this node represents an instance of the standard template + * with the indicated name, or false otherwise. If this returns true, the + * object must be of type XFileDataNode. + */ +bool XFileDataNode:: +is_standard_object(const std::string &template_name) const { + if (_template->is_standard() && + _template->get_name() == template_name) { + return true; + } + + return false; +} + +/** + * Returns a string that represents the type of object this data object + * represents. + */ +std::string XFileDataNode:: +get_type_name() const { + return _template->get_name(); +} diff --git a/pandatool/src/xfile/xFileDataNode.h b/pandatool/src/xfile/xFileDataNode.h new file mode 100644 index 00000000..c04df4a3 --- /dev/null +++ b/pandatool/src/xfile/xFileDataNode.h @@ -0,0 +1,72 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNode.h + * @author drose + * @date 2004-10-08 + */ + +#ifndef XFILEDATANODE_H +#define XFILEDATANODE_H + +#include "pandatoolbase.h" +#include "xFileNode.h" +#include "xFileDataObject.h" +#include "xFileTemplate.h" +#include "pointerTo.h" +#include "dcast.h" + +/** + * This is an abstract base class for an XFileNode which is also an + * XFileDataObject. That is to say, objects that inherit from this class may + * be added to the toplevel X file graph as nodes, and they also may be + * containers for data elements. + * + * Specifically, this is the base class of both XFileDataNodeTemplate and + * XFileDataNodeReference. + */ +class XFileDataNode : public XFileNode, public XFileDataObject { +public: + XFileDataNode(XFile *x_file, const std::string &name, + XFileTemplate *xtemplate); + + virtual bool is_object() const; + virtual bool is_standard_object(const std::string &template_name) const; + virtual std::string get_type_name() const; + + INLINE const XFileDataNode &get_data_child(int n) const; + + INLINE XFileTemplate *get_template() const; + INLINE const std::string &get_template_name() const; + +protected: + PT(XFileTemplate) _template; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileNode::init_type(); + XFileDataObject::init_type(); + register_type(_type_handle, "XFileDataNode", + XFileNode::get_class_type(), + XFileDataObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataNode.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataNodeReference.I b/pandatool/src/xfile/xFileDataNodeReference.I new file mode 100644 index 00000000..ea98beec --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeReference.I @@ -0,0 +1,20 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeReference.I + * @author drose + * @date 2004-10-08 + */ + +/** + * Returns the actual data object being referenced. + */ +INLINE XFileDataNodeTemplate *XFileDataNodeReference:: +get_object() const { + return _object; +} diff --git a/pandatool/src/xfile/xFileDataNodeReference.cxx b/pandatool/src/xfile/xFileDataNodeReference.cxx new file mode 100644 index 00000000..7796aeee --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeReference.cxx @@ -0,0 +1,94 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeReference.cxx + * @author drose + * @date 2004-10-08 + */ + +#include "xFileDataNodeReference.h" +#include "indent.h" + +TypeHandle XFileDataNodeReference::_type_handle; + +/** + * + */ +XFileDataNodeReference:: +XFileDataNodeReference(XFileDataNodeTemplate *object) : + XFileDataNode(object->get_x_file(), object->get_name(), + object->get_template()), + _object(object) +{ + // We steal a copy of the referenced object's children. This is just a one- + // time copy, so if you go and change the list of children of the referenced + // object, it won't be reflected here in the reference. Since presumably + // the reference is only used when parsing static files, that shouldn't be a + // problem; but you do need to be aware of it. + _children = object->_children; + _objects = object->_objects; + _children_by_name = object->_children_by_name; +} + +/** + * Returns true if this node represents an indirect reference to an object + * defined previously in the file. References are generally transparent, so + * in most cases you never need to call this, unless you actually need to + * differentiate between references and instances; you can simply use the + * reference node as if it were itself the object it references. + * + * If this returns true, the node must be of type XFileDataNodeReference. + */ +bool XFileDataNodeReference:: +is_reference() const { + return true; +} + +/** + * Returns true if this kind of data object is a complex object that can hold + * nested data elements, false otherwise. + */ +bool XFileDataNodeReference:: +is_complex_object() const { + return true; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataNodeReference:: +write_text(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << "{ " << _object->get_name() << " }\n"; +} + +/** + * Returns the number of nested data elements within the object. This may be, + * e.g. the size of the array, if it is an array. + */ +int XFileDataNodeReference:: +get_num_elements() const { + return _object->size(); +} + +/** + * Returns the nth nested data element within the object. + */ +XFileDataObject *XFileDataNodeReference:: +get_element(int n) { + return &((*_object)[n]); +} + +/** + * Returns the nested data element within the object that has the indicated + * name. + */ +XFileDataObject *XFileDataNodeReference:: +get_element(const std::string &name) { + return &((*_object)[name]); +} diff --git a/pandatool/src/xfile/xFileDataNodeReference.h b/pandatool/src/xfile/xFileDataNodeReference.h new file mode 100644 index 00000000..f1e9eb4c --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeReference.h @@ -0,0 +1,69 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeReference.h + * @author drose + * @date 2004-10-08 + */ + +#ifndef XFILEDATANODEREFERENCE_H +#define XFILEDATANODEREFERENCE_H + +#include "pandatoolbase.h" +#include "xFileDataNodeTemplate.h" +#include "pointerTo.h" + +/** + * This is a nested reference to an instance of a template object, declared + * via the syntax: + * + * { InstanceName } + * + * in the X File. + */ +class XFileDataNodeReference : public XFileDataNode { +public: + XFileDataNodeReference(XFileDataNodeTemplate *object); + + INLINE XFileTemplate *get_template() const; + INLINE XFileDataNodeTemplate *get_object() const; + + virtual bool is_reference() const; + virtual bool is_complex_object() const; + + virtual void write_text(std::ostream &out, int indent_level) const; + +protected: + virtual int get_num_elements() const; + virtual XFileDataObject *get_element(int n); + virtual XFileDataObject *get_element(const std::string &name); + +private: + PT(XFileDataNodeTemplate) _object; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataNode::init_type(); + register_type(_type_handle, "XFileDataNodeReference", + XFileDataNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataNodeReference.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataNodeTemplate.I b/pandatool/src/xfile/xFileDataNodeTemplate.I new file mode 100644 index 00000000..5e3f6604 --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeTemplate.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeTemplate.I + * @author drose + * @date 2004-10-03 + */ diff --git a/pandatool/src/xfile/xFileDataNodeTemplate.cxx b/pandatool/src/xfile/xFileDataNodeTemplate.cxx new file mode 100644 index 00000000..64647169 --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeTemplate.cxx @@ -0,0 +1,228 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeTemplate.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileDataNodeTemplate.h" +#include "indent.h" +#include "xFileParseData.h" +#include "xLexerDefs.h" +#include "config_xfile.h" + +using std::string; + +TypeHandle XFileDataNodeTemplate::_type_handle; + +/** + * + */ +XFileDataNodeTemplate:: +XFileDataNodeTemplate(XFile *x_file, const string &name, + XFileTemplate *xtemplate) : + XFileDataNode(x_file, name, xtemplate) +{ +} + +/** + * Fills the data node with zero-valued elements appropriate to the template. + */ +void XFileDataNodeTemplate:: +zero_fill() { + _template->fill_zero_data(this); +} + +/** + * Returns true if this kind of data object is a complex object that can hold + * nested data elements, false otherwise. + */ +bool XFileDataNodeTemplate:: +is_complex_object() const { + return true; +} + +/** + * Adds the indicated list of doubles as a data element encountered in the + * parser. It will later be processed by finalize_parse_data(). + */ +void XFileDataNodeTemplate:: +add_parse_double(PTA_double double_list) { + XFileParseData pdata; + pdata._double_list = double_list; + pdata._parse_flags = XFileParseData::PF_double; + + _parse_data_list._list.push_back(pdata); +} + +/** + * Adds the indicated list of ints as a data element encountered in the + * parser. It will later be processed by finalize_parse_data(). + */ +void XFileDataNodeTemplate:: +add_parse_int(PTA_int int_list) { + XFileParseData pdata; + pdata._int_list = int_list; + pdata._parse_flags = XFileParseData::PF_int; + + _parse_data_list._list.push_back(pdata); +} + +/** + * Adds the indicated string as a data element encountered in the parser. It + * will later be processed by finalize_parse_data(). + */ +void XFileDataNodeTemplate:: +add_parse_string(const string &str) { + XFileParseData pdata; + pdata._string = str; + pdata._parse_flags = XFileParseData::PF_string; + + _parse_data_list._list.push_back(pdata); +} + +/** + * Processes all of the data elements added by add_parse_*(), checks them for + * syntactic and semantic correctness against the Template definition, and + * stores the appropriate child data elements. Returns true on success, false + * if there is a mismatch. + */ +bool XFileDataNodeTemplate:: +finalize_parse_data() { + // Recursively walk through our template definition, while simultaneously + // walking through the list of parse data elements we encountered, and re- + // pack them as actual nested elements. + PrevData prev_data; + size_t index = 0; + size_t sub_index = 0; + + if (!_template->repack_data(this, _parse_data_list, + prev_data, index, sub_index)) { + return false; + } + + if (index != _parse_data_list._list.size()) { + xyywarning("Too many data elements in structure."); + } + + return true; +} + +/** + * Adds the indicated element as a nested data element, if this data object + * type supports it. Returns true if added successfully, false if the data + * object type does not support nested data elements. + */ +bool XFileDataNodeTemplate:: +add_element(XFileDataObject *element) { + _nested_elements.push_back(element); + return true; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataNodeTemplate:: +write_text(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << _template->get_name(); + if (has_name()) { + out << " " << get_name(); + } + out << " {\n"; + + NestedElements::const_iterator ni; + for (ni = _nested_elements.begin(); ni != _nested_elements.end(); ++ni) { + (*ni)->write_data(out, indent_level + 2, ";"); + } + + XFileNode::write_text(out, indent_level + 2); + indent(out, indent_level) + << "}\n"; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataNodeTemplate:: +write_data(std::ostream &out, int indent_level, const char *separator) const { + if (!_nested_elements.empty()) { + bool indented = false; + for (size_t i = 0; i < _nested_elements.size() - 1; i++) { + XFileDataObject *object = _nested_elements[i]; + if (object->is_complex_object()) { + // If we have a "complex" nested object, output it on its own line. + if (indented) { + out << "\n"; + indented = false; + } + object->write_data(out, indent_level, ";"); + + } else { + // Otherwise, output them all on the same line. + if (!indented) { + indent(out, indent_level); + indented = true; + } + out << *object << "; "; + } + } + + // The last object is the set is different, because it gets separator + // appended to it, and it always gets a newline. + XFileDataObject *object = _nested_elements.back(); + if (object->is_complex_object()) { + if (indented) { + out << "\n"; + } + string combined_separator = string(";") + string(separator); + object->write_data(out, indent_level, combined_separator.c_str()); + + } else { + if (!indented) { + indent(out, indent_level); + } + out << *object << ";" << separator << "\n"; + } + } +} + +/** + * Returns the number of nested data elements within the object. This may be, + * e.g. the size of the array, if it is an array. + */ +int XFileDataNodeTemplate:: +get_num_elements() const { + return _nested_elements.size(); +} + +/** + * Returns the nth nested data element within the object. + */ +XFileDataObject *XFileDataNodeTemplate:: +get_element(int n) { + nassertr(n >= 0 && n < (int)_nested_elements.size(), nullptr); + return _nested_elements[n]; +} + +/** + * Returns the nested data element within the object that has the indicated + * name. + */ +XFileDataObject *XFileDataNodeTemplate:: +get_element(const string &name) { + int child_index = _template->find_child_index(name); + if (child_index >= 0) { + return get_element(child_index); + } + xfile_cat.warning() + << "\"" << name << "\" not a member of " << _template->get_name() + << "\n"; + return nullptr; +} diff --git a/pandatool/src/xfile/xFileDataNodeTemplate.h b/pandatool/src/xfile/xFileDataNodeTemplate.h new file mode 100644 index 00000000..521c3752 --- /dev/null +++ b/pandatool/src/xfile/xFileDataNodeTemplate.h @@ -0,0 +1,81 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataNodeTemplate.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILEDATANODETEMPLATE_H +#define XFILEDATANODETEMPLATE_H + +#include "pandatoolbase.h" +#include "xFileDataNode.h" +#include "xFileTemplate.h" +#include "xFileParseData.h" +#include "pointerTo.h" +#include "pta_int.h" +#include "pta_double.h" + +/** + * This is a node which contains all of the data elements defined by a + * template. See XFileTemplate for the definition of the template; this class + * only contains the data members for a particular instance of a template. + */ +class XFileDataNodeTemplate : public XFileDataNode { +public: + XFileDataNodeTemplate(XFile *x_file, const std::string &name, + XFileTemplate *xtemplate); + + void zero_fill(); + + virtual bool is_complex_object() const; + + void add_parse_double(PTA_double double_list); + void add_parse_int(PTA_int int_list); + void add_parse_string(const std::string &str); + bool finalize_parse_data(); + + virtual bool add_element(XFileDataObject *element); + + virtual void write_text(std::ostream &out, int indent_level) const; + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual int get_num_elements() const; + virtual XFileDataObject *get_element(int n); + virtual XFileDataObject *get_element(const std::string &name); + +private: + XFileParseDataList _parse_data_list; + + typedef pvector< PT(XFileDataObject) > NestedElements; + NestedElements _nested_elements; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataNode::init_type(); + register_type(_type_handle, "XFileDataNodeTemplate", + XFileDataNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataNodeTemplate.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataObject.I b/pandatool/src/xfile/xFileDataObject.I new file mode 100644 index 00000000..269bc0b0 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObject.I @@ -0,0 +1,304 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObject.I + * @author drose + * @date 2004-10-03 + */ + +/** + * + */ +INLINE XFileDataObject:: +XFileDataObject(const XFileDataDef *data_def) : + _data_def(data_def) +{ +} + +/** + * Returns the data object that this object is represented by, if any, or NULL + * if there is none. + */ +INLINE const XFileDataDef *XFileDataObject:: +get_data_def() const { + return _data_def; +} + +/** + * Stores the indicated integer value into the object, if it makes sense to do + * so. It is an error to call this on an object that cannot accept an integer + * value. + */ +INLINE void XFileDataObject:: +operator = (int int_value) { + set(int_value); +} + +/** + * Stores the indicated floating-point value into the object, if it makes + * sense to do so. It is an error to call this on an object that cannot + * accept a floating-point value. + */ +INLINE void XFileDataObject:: +operator = (double double_value) { + set(double_value); +} + +/** + * Stores the indicated string value into the object, if it makes sense to do + * so. It is an error to call this on an object that cannot accept a string + * value. + */ +INLINE void XFileDataObject:: +operator = (const std::string &string_value) { + set(string_value); +} + +/** + * Stores the indicated Vec2 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store two + * floating-point values. + */ +INLINE void XFileDataObject:: +operator = (const LVecBase2d &vec) { + set(vec); +} + +/** + * Stores the indicated Vec3 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store three + * floating-point values. + */ +INLINE void XFileDataObject:: +operator = (const LVecBase3d &vec) { + set(vec); +} + +/** + * Stores the indicated Vec4 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store four + * floating-point values. + */ +INLINE void XFileDataObject:: +operator = (const LVecBase4d &vec) { + set(vec); +} + +/** + * Stores the indicated Matrix value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store sixteen + * floating-point values. + */ +INLINE void XFileDataObject:: +operator = (const LMatrix4d &mat) { + set(mat); +} + +/** + * Stores the indicated integer value into the object, if it makes sense to do + * so. It is an error to call this on an object that cannot accept an integer + * value. + */ +INLINE void XFileDataObject:: +set(int int_value) { + set_int_value(int_value); +} + +/** + * Stores the indicated floating-point value into the object, if it makes + * sense to do so. It is an error to call this on an object that cannot + * accept a floating-point value. + */ +INLINE void XFileDataObject:: +set(double double_value) { + set_double_value(double_value); +} + +/** + * Stores the indicated string value into the object, if it makes sense to do + * so. It is an error to call this on an object that cannot accept a string + * value. + */ +INLINE void XFileDataObject:: +set(const std::string &string_value) { + set_string_value(string_value); +} + +/** + * Stores the indicated Vec2 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store two + * floating-point values. + */ +INLINE void XFileDataObject:: +set(const LVecBase2d &vec) { + store_double_array(2, vec.get_data()); +} + +/** + * Stores the indicated Vec3 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store three + * floating-point values. + */ +INLINE void XFileDataObject:: +set(const LVecBase3d &vec) { + store_double_array(3, vec.get_data()); +} + +/** + * Stores the indicated Vec4 value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store four + * floating-point values. + */ +INLINE void XFileDataObject:: +set(const LVecBase4d &vec) { + store_double_array(4, vec.get_data()); +} + +/** + * Stores the indicated Matrix value into the object, if it makes sense to do + * so. It is an error to call this on an object that does not store sixteen + * floating-point values. + */ +INLINE void XFileDataObject:: +set(const LMatrix4d &mat) { + store_double_array(16, mat.get_data()); +} + +/** + * Unambiguously returns the object's representation as an integer, or 0 if + * the object has no integer representation. See also get_data_def() to + * determine what kind of representation this object has. + */ +INLINE int XFileDataObject:: +i() const { + return get_int_value(); +} + +/** + * Unambiguously returns the object's representation as a double, or 0.0 if + * the object has no double representation. See also get_data_def() to + * determine what kind of representation this object has. + */ +INLINE double XFileDataObject:: +d() const { + return get_double_value(); +} + +/** + * Unambiguously returns the object's representation as a string, or empty + * string if the object has no string representation. See also get_data_def() + * to determine what kind of representation this object has. + */ +INLINE std::string XFileDataObject:: +s() const { + return get_string_value(); +} + +/** + * Returns the object's representation as an LVecBase2d. It is an error if + * the object does not have two nested objects that store a double value. + */ +INLINE LVecBase2d XFileDataObject:: +vec2() const { + LVecBase2d vec; + get_double_array(2, &vec[0]); + return vec; +} + +/** + * Returns the object's representation as an LVecBase3d. It is an error if + * the object does not have three nested objects that store a double value. + */ +INLINE LVecBase3d XFileDataObject:: +vec3() const { + LVecBase3d vec; + get_double_array(3, &vec[0]); + return vec; +} + +/** + * Returns the object's representation as an LVecBase4d. It is an error if + * the object does not have four nested objects that store a double value. + */ +INLINE LVecBase4d XFileDataObject:: +vec4() const { + LVecBase4d vec; + get_double_array(4, &vec[0]); + return vec; +} + +/** + * Returns the object's representation as an LMatrix4d. It is an error if the + * object does not have sixteen nested objects that store a double value. + */ +INLINE LMatrix4d XFileDataObject:: +mat4() const { + LMatrix4d mat; + get_double_array(16, &mat(0, 0)); + return mat; +} + +/** + * Returns the number of nested data objects within this object. + */ +INLINE int XFileDataObject:: +size() const { + return get_num_elements(); +} + +/** + * Returns the nth nested object within this object. Call get_num_children() + * to determine the number of nested objects. + */ +INLINE const XFileDataObject &XFileDataObject:: +operator [] (int n) const { + const XFileDataObject *element = ((XFileDataObject *)this)->get_element(n); + nassertr(element != nullptr, *this); + return *element; +} + +/** + * Returns the named nested object within this object. It is an error if the + * named object does not exist. Call find_child() instead if there is any + * doubt. + */ +INLINE const XFileDataObject &XFileDataObject:: +operator [] (const std::string &name) const { + const XFileDataObject *element = ((XFileDataObject *)this)->get_element(name); + nassertr(element != nullptr, *this); + return *element; +} + +/** + * Returns the nth nested object within this object. Call get_num_children() + * to determine the number of nested objects. + */ +INLINE XFileDataObject &XFileDataObject:: +operator [] (int n) { + XFileDataObject *element = get_element(n); + nassertr(element != nullptr, *this); + return *element; +} + +/** + * Returns the named nested object within this object. It is an error if the + * named object does not exist. Call find_child() instead if there is any + * doubt. + */ +INLINE XFileDataObject &XFileDataObject:: +operator [] (const std::string &name) { + XFileDataObject *element = get_element(name); + nassertr(element != nullptr, *this); + return *element; +} + +INLINE std::ostream & +operator << (std::ostream &out, const XFileDataObject &data_object) { + data_object.output_data(out); + return out; +} diff --git a/pandatool/src/xfile/xFileDataObject.cxx b/pandatool/src/xfile/xFileDataObject.cxx new file mode 100644 index 00000000..f3cf0b03 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObject.cxx @@ -0,0 +1,306 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObject.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileDataObject.h" +#include "xFileTemplate.h" +#include "xFile.h" +#include "xFileDataNodeTemplate.h" +#include "xFileDataObjectInteger.h" +#include "xFileDataObjectDouble.h" +#include "xFileDataObjectString.h" +#include "config_xfile.h" +#include "indent.h" + +using std::string; + +TypeHandle XFileDataObject::_type_handle; + +/** + * + */ +XFileDataObject:: +~XFileDataObject() { +} + +/** + * Returns true if this kind of data object is a complex object that can hold + * nested data elements, false otherwise. + */ +bool XFileDataObject:: +is_complex_object() const { + return false; +} + +/** + * Returns a string that represents the type of object this data object + * represents. + */ +string XFileDataObject:: +get_type_name() const { + return get_type().get_name(); +} + +/** + * Appends a new integer value to the data object, if it makes sense to do so. + * Normally, this is valid only for a DataObjectArray, or in certain special + * cases for a DataNodeTemplate. + */ +XFileDataObject &XFileDataObject:: +add_int(int int_value) { + XFileDataObject *object = + new XFileDataObjectInteger(get_data_def(), int_value); + add_element(object); + return *object; +} + +/** + * Appends a new floating-point value to the data object, if it makes sense to + * do so. Normally, this is valid only for a DataObjectArray, or in certain + * special cases for a DataNodeTemplate. + */ +XFileDataObject &XFileDataObject:: +add_double(double double_value) { + XFileDataObject *object = + new XFileDataObjectDouble(get_data_def(), double_value); + add_element(object); + return *object; +} + +/** + * Appends a new string value to the data object, if it makes sense to do so. + * Normally, this is valid only for a DataObjectArray, or in certain special + * cases for a DataNodeTemplate. + */ +XFileDataObject &XFileDataObject:: +add_string(const string &string_value) { + XFileDataObject *object = + new XFileDataObjectString(get_data_def(), string_value); + add_element(object); + return *object; +} + +/** + * Appends a new Vector instance. + */ +XFileDataObject &XFileDataObject:: +add_Vector(XFile *x_file, const LVecBase3d &vector) { + XFileTemplate *xtemplate = XFile::find_standard_template("Vector"); + nassertr(xtemplate != nullptr, *this); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(x_file, "", xtemplate); + add_element(node); + node->zero_fill(); + + node->set(vector); + + return *node; +} + +/** + * Appends a new MeshFace instance. + */ +XFileDataObject &XFileDataObject:: +add_MeshFace(XFile *x_file) { + XFileTemplate *xtemplate = XFile::find_standard_template("MeshFace"); + nassertr(xtemplate != nullptr, *this); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(x_file, "", xtemplate); + add_element(node); + node->zero_fill(); + + return *node; +} + +/** + * Appends a new IndexedColor instance. + */ +XFileDataObject &XFileDataObject:: +add_IndexedColor(XFile *x_file, int index, const LColor &color) { + XFileTemplate *xtemplate = XFile::find_standard_template("IndexedColor"); + nassertr(xtemplate != nullptr, *this); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(x_file, "", xtemplate); + add_element(node); + node->zero_fill(); + + (*node)["index"] = index; + (*node)["indexColor"] = LCAST(double, color); + + return *node; +} + +/** + * Appends a new Coords2d instance. + */ +XFileDataObject &XFileDataObject:: +add_Coords2d(XFile *x_file, const LVecBase2d &coords) { + XFileTemplate *xtemplate = XFile::find_standard_template("Coords2d"); + nassertr(xtemplate != nullptr, *this); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(x_file, "", xtemplate); + add_element(node); + node->zero_fill(); + + node->set(coords); + + return *node; +} + +/** + * Adds the indicated element as a nested data element, if this data object + * type supports it. Returns true if added successfully, false if the data + * object type does not support nested data elements. + */ +bool XFileDataObject:: +add_element(XFileDataObject *element) { + return false; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObject:: +output_data(std::ostream &out) const { + out << "(" << get_type() << "::output_data() not implemented.)"; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObject:: +write_data(std::ostream &out, int indent_level, const char *) const { + indent(out, indent_level) + << "(" << get_type() << "::write_data() not implemented.)\n"; +} + +/** + * Sets the object's value as an integer, if this is legal. + */ +void XFileDataObject:: +set_int_value(int int_value) { + xfile_cat.error() + << get_type_name() << " does not support integer values.\n"; +} + +/** + * Sets the object's value as a floating-point number, if this is legal. + */ +void XFileDataObject:: +set_double_value(double double_value) { + xfile_cat.error() + << get_type_name() << " does not support floating-point values.\n"; +} + +/** + * Sets the object's value as a string, if this is legal. + */ +void XFileDataObject:: +set_string_value(const string &string_value) { + xfile_cat.error() + << get_type_name() << " does not support string values.\n"; +} + +/** + * Stores the indicated array of doubles in the nested elements within this + * object. There must be exactly the indicated number of nested values, and + * they must all accept a double. + */ +void XFileDataObject:: +store_double_array(int num_elements, const double *values) { + if (get_num_elements() != num_elements) { + xfile_cat.error() + << get_type_name() << " does not accept " + << num_elements << " values.\n"; + return; + } + + for (int i = 0; i < num_elements; i++) { + get_element(i)->set_double_value(values[i]); + } +} + + +/** + * Returns the object's representation as an integer, if it has one. + */ +int XFileDataObject:: +get_int_value() const { + return 0; +} + +/** + * Returns the object's representation as a double, if it has one. + */ +double XFileDataObject:: +get_double_value() const { + return 0.0; +} + +/** + * Returns the object's representation as a string, if it has one. + */ +string XFileDataObject:: +get_string_value() const { + return string(); +} + +/** + * Fills the indicated array of doubles with the values from the nested + * elements within this object. There must be exactly the indicated number of + * nested values, and they must all return a double. + */ +void XFileDataObject:: +get_double_array(int num_elements, double *values) const { + if (get_num_elements() != num_elements) { + xfile_cat.error() + << get_type_name() << " does not contain " + << num_elements << " values.\n"; + return; + } + + for (int i = 0; i < num_elements; i++) { + values[i] = ((XFileDataObject *)this)->get_element(i)->get_double_value(); + } +} + +/** + * Returns the number of nested data elements within the object. This may be, + * e.g. the size of the array, if it is an array. + */ +int XFileDataObject:: +get_num_elements() const { + return 0; +} + +/** + * Returns the nth nested data element within the object. + */ +XFileDataObject *XFileDataObject:: +get_element(int n) { + xfile_cat.warning() + << "Looking for [" << n << "] within data object of type " + << get_type_name() << ", does not support nested objects.\n"; + return nullptr; +} + +/** + * Returns the nested data element within the object that has the indicated + * name. + */ +XFileDataObject *XFileDataObject:: +get_element(const string &name) { + xfile_cat.warning() + << "Looking for [\"" << name << "\"] within data object of type " + << get_type_name() << ", does not support nested objects.\n"; + return nullptr; +} diff --git a/pandatool/src/xfile/xFileDataObject.h b/pandatool/src/xfile/xFileDataObject.h new file mode 100644 index 00000000..814d5f3c --- /dev/null +++ b/pandatool/src/xfile/xFileDataObject.h @@ -0,0 +1,133 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObject.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILEDATAOBJECT_H +#define XFILEDATAOBJECT_H + +#include "pandatoolbase.h" +#include "referenceCount.h" +#include "pointerTo.h" +#include "dcast.h" +#include "luse.h" + +class XFile; +class XFileDataDef; + +/** + * The abstract base class for a number of different types of data elements + * that may be stored in the X file. + */ +class XFileDataObject : virtual public ReferenceCount { +public: + INLINE XFileDataObject(const XFileDataDef *data_def = nullptr); + virtual ~XFileDataObject(); + + INLINE const XFileDataDef *get_data_def() const; + + virtual bool is_complex_object() const; + virtual std::string get_type_name() const; + + INLINE void operator = (int int_value); + INLINE void operator = (double double_value); + INLINE void operator = (const std::string &string_value); + INLINE void operator = (const LVecBase2d &vec); + INLINE void operator = (const LVecBase3d &vec); + INLINE void operator = (const LVecBase4d &vec); + INLINE void operator = (const LMatrix4d &mat); + + INLINE void set(int int_value); + INLINE void set(double double_value); + INLINE void set(const std::string &string_value); + INLINE void set(const LVecBase2d &vec); + INLINE void set(const LVecBase3d &vec); + INLINE void set(const LVecBase4d &vec); + INLINE void set(const LMatrix4d &mat); + + INLINE int i() const; + INLINE double d() const; + INLINE std::string s() const; + INLINE LVecBase2d vec2() const; + INLINE LVecBase3d vec3() const; + INLINE LVecBase4d vec4() const; + INLINE LMatrix4d mat4() const; + + INLINE int size() const; + INLINE const XFileDataObject &operator [] (int n) const; + INLINE const XFileDataObject &operator [] (const std::string &name) const; + + INLINE XFileDataObject &operator [] (int n); + INLINE XFileDataObject &operator [] (const std::string &name); + + // The following methods can be used to add elements of a specific type to a + // complex object, e.g. an array or a template object. + + XFileDataObject &add_int(int int_value); + XFileDataObject &add_double(double double_value); + XFileDataObject &add_string(const std::string &string_value); + + // The following methods can be used to add elements of a specific type, + // based on one of the standard templates. + + XFileDataObject &add_Vector(XFile *x_file, const LVecBase3d &vector); + XFileDataObject &add_MeshFace(XFile *x_file); + XFileDataObject &add_IndexedColor(XFile *x_file, int index, + const LColor &color); + XFileDataObject &add_Coords2d(XFile *x_file, const LVecBase2d &coords); + +public: + virtual bool add_element(XFileDataObject *element); + + virtual void output_data(std::ostream &out) const; + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual void set_int_value(int int_value); + virtual void set_double_value(double double_value); + virtual void set_string_value(const std::string &string_value); + void store_double_array(int num_elements, const double *values); + + virtual int get_int_value() const; + virtual double get_double_value() const; + virtual std::string get_string_value() const; + void get_double_array(int num_elements, double *values) const; + + virtual int get_num_elements() const; + virtual XFileDataObject *get_element(int n); + virtual XFileDataObject *get_element(const std::string &name); + + const XFileDataDef *_data_def; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + ReferenceCount::init_type(); + register_type(_type_handle, "XFileDataObject", + ReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +INLINE std::ostream &operator << (std::ostream &out, const XFileDataObject &data_object); + +#include "xFileDataObject.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataObjectArray.I b/pandatool/src/xfile/xFileDataObjectArray.I new file mode 100644 index 00000000..fefe7e60 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectArray.I @@ -0,0 +1,21 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectArray.I + * @author drose + * @date 2004-10-07 + */ + +/** + * + */ +XFileDataObjectArray:: +XFileDataObjectArray(const XFileDataDef *data_def) : + XFileDataObject(data_def) +{ +} diff --git a/pandatool/src/xfile/xFileDataObjectArray.cxx b/pandatool/src/xfile/xFileDataObjectArray.cxx new file mode 100644 index 00000000..e648ef2c --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectArray.cxx @@ -0,0 +1,103 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectArray.cxx + * @author drose + * @date 2004-10-07 + */ + +#include "xFileDataObjectArray.h" +#include "string_utils.h" +#include "indent.h" + +TypeHandle XFileDataObjectArray::_type_handle; + +/** + * Returns true if this kind of data object is a complex object that can hold + * nested data elements, false otherwise. + */ +bool XFileDataObjectArray:: +is_complex_object() const { + return true; +} + +/** + * Adds the indicated element as a nested data element, if this data object + * type supports it. Returns true if added successfully, false if the data + * object type does not support nested data elements. + */ +bool XFileDataObjectArray:: +add_element(XFileDataObject *element) { + _nested_elements.push_back(element); + return true; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectArray:: +write_data(std::ostream &out, int indent_level, const char *separator) const { + if (!_nested_elements.empty()) { + bool indented = false; + for (size_t i = 0; i < _nested_elements.size() - 1; i++) { + XFileDataObject *object = _nested_elements[i]; + if (object->is_complex_object() || + _nested_elements.size() > 16) { + // If we have a "complex" nested object, or more than 16 elements in + // the array, output it on its own line. + if (indented) { + out << "\n"; + indented = false; + } + object->write_data(out, indent_level, ","); + + } else { + // Otherwise, output them all on the same line. + if (!indented) { + indent(out, indent_level); + indented = true; + } + out << *object << ", "; + } + } + + // The last object in the set is different, because it gets separator + // instead of a semicolon, and it always gets a newline. + XFileDataObject *object = _nested_elements.back(); + if (object->is_complex_object()) { + if (indented) { + out << "\n"; + } + object->write_data(out, indent_level, separator); + + } else { + if (!indented) { + indent(out, indent_level); + } + out << *object << separator << "\n"; + } + } +} + +/** + * Returns the number of nested data elements within the object. This may be, + * e.g. the size of the array, if it is an array. + */ +int XFileDataObjectArray:: +get_num_elements() const { + return _nested_elements.size(); +} + +/** + * Returns the nth nested data element within the object. + */ +XFileDataObject *XFileDataObjectArray:: +get_element(int n) { + nassertr(n >= 0 && n < (int)_nested_elements.size(), nullptr); + return _nested_elements[n]; +} diff --git a/pandatool/src/xfile/xFileDataObjectArray.h b/pandatool/src/xfile/xFileDataObjectArray.h new file mode 100644 index 00000000..3b9000c9 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectArray.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectArray.h + * @author drose + * @date 2004-10-07 + */ + +#ifndef XFILEDATAOBJECTARRAY_H +#define XFILEDATAOBJECTARRAY_H + +#include "pandatoolbase.h" +#include "xFileDataObject.h" + +/** + * An array of nested data elements. + */ +class XFileDataObjectArray : public XFileDataObject { +public: + INLINE XFileDataObjectArray(const XFileDataDef *data_def); + + virtual bool is_complex_object() const; + + virtual bool add_element(XFileDataObject *element); + + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual int get_num_elements() const; + virtual XFileDataObject *get_element(int n); + +private: + typedef pvector< PT(XFileDataObject) > NestedElements; + NestedElements _nested_elements; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataObject::init_type(); + register_type(_type_handle, "XFileDataObjectArray", + XFileDataObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataObjectArray.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataObjectDouble.I b/pandatool/src/xfile/xFileDataObjectDouble.I new file mode 100644 index 00000000..339d3b08 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectDouble.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectDouble.I + * @author drose + * @date 2004-10-07 + */ diff --git a/pandatool/src/xfile/xFileDataObjectDouble.cxx b/pandatool/src/xfile/xFileDataObjectDouble.cxx new file mode 100644 index 00000000..d17b2bbb --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectDouble.cxx @@ -0,0 +1,91 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectDouble.cxx + * @author drose + * @date 2004-10-07 + */ + +#include "xFileDataObjectDouble.h" +#include "string_utils.h" +#include "indent.h" + +TypeHandle XFileDataObjectDouble::_type_handle; + +/** + * + */ +XFileDataObjectDouble:: +XFileDataObjectDouble(const XFileDataDef *data_def, double value) : + XFileDataObject(data_def), + _value(value) +{ +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectDouble:: +output_data(std::ostream &out) const { + out << get_string_value(); +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectDouble:: +write_data(std::ostream &out, int indent_level, const char *separator) const { + indent(out, indent_level) + << get_string_value() << separator << "\n"; +} + +/** + * Sets the object's value as an integer, if this is legal. + */ +void XFileDataObjectDouble:: +set_int_value(int int_value) { + _value = (double)int_value; +} + +/** + * Sets the object's value as a floating-point number, if this is legal. + */ +void XFileDataObjectDouble:: +set_double_value(double double_value) { + _value = double_value; +} + +/** + * Returns the object's representation as an integer, if it has one. + */ +int XFileDataObjectDouble:: +get_int_value() const { + return (int)_value; +} + +/** + * Returns the object's representation as a double, if it has one. + */ +double XFileDataObjectDouble:: +get_double_value() const { + return _value; +} + +/** + * Returns the object's representation as a string, if it has one. + */ +std::string XFileDataObjectDouble:: +get_string_value() const { + // It's important to format with a decimal point, even if the value is + // integral, since the DirectX .x reader differentiates betweens doubles and + // integers on parsing. + char buffer[128]; + sprintf(buffer, "%f", _value); + + return buffer; +} diff --git a/pandatool/src/xfile/xFileDataObjectDouble.h b/pandatool/src/xfile/xFileDataObjectDouble.h new file mode 100644 index 00000000..4450f452 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectDouble.h @@ -0,0 +1,63 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectDouble.h + * @author drose + * @date 2004-10-07 + */ + +#ifndef XFILEDATAOBJECTDOUBLE_H +#define XFILEDATAOBJECTDOUBLE_H + +#include "pandatoolbase.h" +#include "xFileDataObject.h" + +/** + * An double-valued data element. This matches one double data member of a + * template, or a single element of an double array. + */ +class XFileDataObjectDouble : public XFileDataObject { +public: + XFileDataObjectDouble(const XFileDataDef *data_def, double value); + + virtual void output_data(std::ostream &out) const; + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual void set_int_value(int int_value); + virtual void set_double_value(double double_value); + + virtual int get_int_value() const; + virtual double get_double_value() const; + virtual std::string get_string_value() const; + +private: + double _value; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataObject::init_type(); + register_type(_type_handle, "XFileDataObjectDouble", + XFileDataObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataObjectDouble.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataObjectInteger.I b/pandatool/src/xfile/xFileDataObjectInteger.I new file mode 100644 index 00000000..07f27b7a --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectInteger.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectInteger.I + * @author drose + * @date 2004-10-07 + */ diff --git a/pandatool/src/xfile/xFileDataObjectInteger.cxx b/pandatool/src/xfile/xFileDataObjectInteger.cxx new file mode 100644 index 00000000..ee3c539d --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectInteger.cxx @@ -0,0 +1,77 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectInteger.cxx + * @author drose + * @date 2004-10-07 + */ + +#include "xFileDataObjectInteger.h" +#include "string_utils.h" +#include "indent.h" + +TypeHandle XFileDataObjectInteger::_type_handle; + +/** + * + */ +XFileDataObjectInteger:: +XFileDataObjectInteger(const XFileDataDef *data_def, int value) : + XFileDataObject(data_def), + _value(value) +{ +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectInteger:: +output_data(std::ostream &out) const { + out << _value; +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectInteger:: +write_data(std::ostream &out, int indent_level, const char *separator) const { + indent(out, indent_level) + << _value << separator << "\n"; +} + +/** + * Sets the object's value as an integer, if this is legal. + */ +void XFileDataObjectInteger:: +set_int_value(int int_value) { + _value = int_value; +} + +/** + * Returns the object's representation as an integer, if it has one. + */ +int XFileDataObjectInteger:: +get_int_value() const { + return _value; +} + +/** + * Returns the object's representation as a double, if it has one. + */ +double XFileDataObjectInteger:: +get_double_value() const { + return _value; +} + +/** + * Returns the object's representation as a string, if it has one. + */ +std::string XFileDataObjectInteger:: +get_string_value() const { + return format_string(_value); +} diff --git a/pandatool/src/xfile/xFileDataObjectInteger.h b/pandatool/src/xfile/xFileDataObjectInteger.h new file mode 100644 index 00000000..b185ea93 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectInteger.h @@ -0,0 +1,62 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectInteger.h + * @author drose + * @date 2004-10-07 + */ + +#ifndef XFILEDATAOBJECTINTEGER_H +#define XFILEDATAOBJECTINTEGER_H + +#include "pandatoolbase.h" +#include "xFileDataObject.h" + +/** + * An integer-valued data element. This matches one integer data member of a + * template, or a single element of an integer array. + */ +class XFileDataObjectInteger : public XFileDataObject { +public: + XFileDataObjectInteger(const XFileDataDef *data_def, int value); + + virtual void output_data(std::ostream &out) const; + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual void set_int_value(int int_value); + + virtual int get_int_value() const; + virtual double get_double_value() const; + virtual std::string get_string_value() const; + +private: + int _value; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataObject::init_type(); + register_type(_type_handle, "XFileDataObjectInteger", + XFileDataObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataObjectInteger.I" + +#endif diff --git a/pandatool/src/xfile/xFileDataObjectString.I b/pandatool/src/xfile/xFileDataObjectString.I new file mode 100644 index 00000000..8e55118d --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectString.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectString.I + * @author drose + * @date 2004-10-08 + */ diff --git a/pandatool/src/xfile/xFileDataObjectString.cxx b/pandatool/src/xfile/xFileDataObjectString.cxx new file mode 100644 index 00000000..99ef31a8 --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectString.cxx @@ -0,0 +1,97 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectString.cxx + * @author drose + * @date 2004-10-08 + */ + +#include "xFileDataObjectString.h" +#include "string_utils.h" +#include "indent.h" + +using std::string; + +TypeHandle XFileDataObjectString::_type_handle; + +/** + * + */ +XFileDataObjectString:: +XFileDataObjectString(const XFileDataDef *data_def, const string &value) : + XFileDataObject(data_def), + _value(value) +{ +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectString:: +output_data(std::ostream &out) const { + enquote_string(out); +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileDataObjectString:: +write_data(std::ostream &out, int indent_level, const char *separator) const { + indent(out, indent_level); + enquote_string(out); + out << separator << "\n"; +} + +/** + * Sets the object's value as a string, if this is legal. + */ +void XFileDataObjectString:: +set_string_value(const string &string_value) { + _value = string_value; +} + +/** + * Returns the object's representation as a string, if it has one. + */ +string XFileDataObjectString:: +get_string_value() const { + return _value; +} + +/** + * Writes the string to the output stream without quotation marks, quoting + * special characters as needed. + */ +void XFileDataObjectString:: +enquote_string(std::ostream &out) const { + // Actually, the XFile spec doesn't tell us how to escape special characters + // within quotation marks. We'll just take a stab in the dark here. + + out << '"'; + string::const_iterator si; + for (si = _value.begin(); si != _value.end(); ++si) { + switch (*si) { + case '\n': + out << "\\n"; + break; + + case '\r': + out << "\\r"; + break; + + case '"': + case '\\': + out << '\\' << (*si); + break; + + default: + out << (*si); + } + } + out << '"'; +} diff --git a/pandatool/src/xfile/xFileDataObjectString.h b/pandatool/src/xfile/xFileDataObjectString.h new file mode 100644 index 00000000..1fd10dea --- /dev/null +++ b/pandatool/src/xfile/xFileDataObjectString.h @@ -0,0 +1,61 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileDataObjectString.h + * @author drose + * @date 2004-10-08 + */ + +#ifndef XFILEDATAOBJECTSTRING_H +#define XFILEDATAOBJECTSTRING_H + +#include "pandatoolbase.h" +#include "xFileDataObject.h" + +/** + * An string-valued data element. This matches one string data member of a + * template, or a single element of an string array. + */ +class XFileDataObjectString : public XFileDataObject { +public: + XFileDataObjectString(const XFileDataDef *data_def, const std::string &value); + + virtual void output_data(std::ostream &out) const; + virtual void write_data(std::ostream &out, int indent_level, + const char *separator) const; + +protected: + virtual void set_string_value(const std::string &string_value); + virtual std::string get_string_value() const; + +private: + void enquote_string(std::ostream &out) const; + + std::string _value; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileDataObject::init_type(); + register_type(_type_handle, "XFileDataObjectString", + XFileDataObject::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "xFileDataObjectString.I" + +#endif diff --git a/pandatool/src/xfile/xFileNode.I b/pandatool/src/xfile/xFileNode.I new file mode 100644 index 00000000..735c2dce --- /dev/null +++ b/pandatool/src/xfile/xFileNode.I @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileNode.I + * @author drose + * @date 2004-10-03 + */ + +/** + * + */ +INLINE XFileNode:: +XFileNode(XFile *x_file) : + Namable(), + _x_file(x_file) +{ +} + +/** + * + */ +INLINE XFile *XFileNode:: +get_x_file() const { + return _x_file; +} + +/** + * Returns the list of children of this node. This list includes templates as + * well as data objects. + */ +INLINE int XFileNode:: +get_num_children() const { + return _children.size(); +} + +/** + * Returns the nth child of this node. This list includes templates as well + * as data objects. + */ +INLINE XFileNode *XFileNode:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), nullptr); + return _children[n]; +} + +/** + * Returns the list of child objects of this node. This list does not include + * template definitions; it is strictly the list of children that are also + * data objects (instances of templates). + */ +INLINE int XFileNode:: +get_num_objects() const { + return _objects.size(); +} + +/** + * Returns the nth child object of this node. This list does not include + * template definitions; it is strictly the list of children that are also + * data objects (instances of templates). + */ +INLINE XFileDataNode *XFileNode:: +get_object(int n) const { + nassertr(n >= 0 && n < (int)_objects.size(), nullptr); + return _objects[n]; +} diff --git a/pandatool/src/xfile/xFileNode.cxx b/pandatool/src/xfile/xFileNode.cxx new file mode 100644 index 00000000..51c795e4 --- /dev/null +++ b/pandatool/src/xfile/xFileNode.cxx @@ -0,0 +1,499 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileNode.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileNode.h" +#include "windowsGuid.h" +#include "xFile.h" +#include "xLexerDefs.h" +#include "xFileParseData.h" +#include "xFile.h" +#include "xFileDataNodeTemplate.h" +#include "filename.h" +#include "string_utils.h" + +using std::string; + +TypeHandle XFileNode::_type_handle; + +/** + * + */ +XFileNode:: +XFileNode(XFile *x_file, const string &name) : + Namable(), + _x_file(x_file) +{ + if (x_file && x_file->_keep_names) { + set_name(name); + } else { + set_name(make_nice_name(name)); + } +} + +/** + * + */ +XFileNode:: +~XFileNode() { + clear(); +} + +/** + * Returns the child with the indicated name, if any, or NULL if none. + */ +XFileNode *XFileNode:: +find_child(const string &name) const { + ChildrenByName::const_iterator ni; + ni = _children_by_name.find(downcase(name)); + if (ni != _children_by_name.end()) { + return get_child((*ni).second); + } + + return nullptr; +} + +/** + * Returns the index number of the child with the indicated name, if any, or + * -1 if none. + */ +int XFileNode:: +find_child_index(const string &name) const { + ChildrenByName::const_iterator ni; + ni = _children_by_name.find(downcase(name)); + if (ni != _children_by_name.end()) { + return (*ni).second; + } + + return -1; +} + +/** + * Returns the index number of the indicated child, or -1 if none. + */ +int XFileNode:: +find_child_index(const XFileNode *child) const { + for (int i = 0; i < (int)_children.size(); i++) { + if (_children[i] == child) { + return i; + } + } + + return -1; +} + +/** + * Returns the first child or descendent found with the indicated name after a + * depth-first search, if any, or NULL if none. + */ +XFileNode *XFileNode:: +find_descendent(const string &name) const { + XFileNode *child = find_child(name); + if (child != nullptr) { + return child; + } + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + XFileNode *child = (*ci)->find_descendent(name); + if (child != nullptr){ + return child; + } + } + + return nullptr; +} + +/** + * Returns true if this node has a GUID associated. + */ +bool XFileNode:: +has_guid() const { + return false; +} + +/** + * If has_guid() returned true, returns the particular GUID associated with + * this node. + */ +const WindowsGuid &XFileNode:: +get_guid() const { + static WindowsGuid empty; + return empty; +} + +/** + * Returns true if this node represents the definition of some template. This + * is the template definition, not an actual data object that represents an + * instance of the template. If the file strictly uses standard templates, + * the presence of template definitions is optional. + * + * If this returns true, the node must be of type XFileTemplate. + */ +bool XFileNode:: +is_template_def() const { + return false; +} + +/** + * Returns true if this node represents an indirect reference to an object + * defined previously in the file. References are generally transparent, so + * in most cases you never need to call this, unless you actually need to + * differentiate between references and instances; you can simply use the + * reference node as if it were itself the object it references. + * + * If this returns true, the node must be of type XFileDataNodeReference. + */ +bool XFileNode:: +is_reference() const { + return false; +} + +/** + * Returns true if this node represents a data object that is the instance of + * some template, or false otherwise. This also returns true for references + * to objects (which are generally treated just like the objects themselves). + * + * If this returns true, the node must be of type XFileDataNode (it is either + * an XFileDataNodeTemplate or an XFileDataNodeReference). + */ +bool XFileNode:: +is_object() const { + return false; +} + +/** + * Returns true if this node represents an instance of the standard template + * with the indicated name, or false otherwise. This returns also returns + * true for references to standard objects. + * + * If this returns true, the node must be of type XFileDataNode (it is either + * an XFileDataNodeTemplate or an XFileDataNodeReference). + */ +bool XFileNode:: +is_standard_object(const string &template_name) const { + return false; +} + +/** + * Adds the indicated node as a child of this node. + */ +void XFileNode:: +add_child(XFileNode *node) { + if (node->has_name()) { + _children_by_name[downcase(node->get_name())] = (int)_children.size(); + } + if (node->has_guid()) { + _x_file->_nodes_by_guid[node->get_guid()] = node; + } + if (node->is_of_type(XFileDataNode::get_class_type())) { + _objects.push_back(DCAST(XFileDataNode, node)); + } + _children.push_back(node); +} + +/** + * Removes all children from the node, and otherwise resets it to its initial + * state. + */ +void XFileNode:: +clear() { + _children.clear(); + _objects.clear(); + _children_by_name.clear(); +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileNode:: +write_text(std::ostream &out, int indent_level) const { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + (*ci)->write_text(out, indent_level); + } +} + +/** + * This is called on the template that defines an object, once the data for + * the object has been parsed. It is responsible for identifying which + * component of the template owns each data element, and packing the data + * elements appropriately back into the object. + * + * It returns true on success, or false on an error (e.g. not enough data + * elements, mismatched data type). + */ +bool XFileNode:: +repack_data(XFileDataObject *object, + const XFileParseDataList &parse_data_list, + XFileNode::PrevData &prev_data, + size_t &index, size_t &sub_index) const { + // This method should be specialized for data types that actually consume a + // data element. Here in the base class, it just walks through its + // children, asking each one to pull off the appropriate number of data + // elements. + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + if (!(*ci)->repack_data(object, parse_data_list, + prev_data, index, sub_index)) { + return false; + } + } + + return true; +} + +/** + * This is similar to repack_data(), except it is used to fill the initial + * values for a newly-created template object to zero. + */ +bool XFileNode:: +fill_zero_data(XFileDataObject *object) const { + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + if (!(*ci)->fill_zero_data(object)) { + return false; + } + } + + return true; +} + +/** + * Returns true if the node, particularly a template node, is structurally + * equivalent to the other node (which must be of the same type). This checks + * data element types, but does not compare data element names. + */ +bool XFileNode:: +matches(const XFileNode *other) const { + if (other->get_type() != get_type()) { + return false; + } + + if (other->get_num_children() != get_num_children()) { + return false; + } + + for (int i = 0; i < get_num_children(); i++) { + if (!get_child(i)->matches(other->get_child(i))) { + return false; + } + } + + return true; +} + +/** + * Creates a new Mesh instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_Mesh(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("Mesh"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new MeshNormals instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_MeshNormals(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("MeshNormals"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new MeshVertexColors instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_MeshVertexColors(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("MeshVertexColors"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new MeshTextureCoords instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_MeshTextureCoords(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("MeshTextureCoords"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new MeshMaterialList instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_MeshMaterialList(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("MeshMaterialList"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new Material instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_Material(const string &name, const LColor &face_color, + double power, const LRGBColor &specular_color, + const LRGBColor &emissive_color) { + XFileTemplate *xtemplate = XFile::find_standard_template("Material"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + (*node)["faceColor"]["red"] = face_color[0]; + (*node)["faceColor"]["green"] = face_color[1]; + (*node)["faceColor"]["blue"] = face_color[2]; + (*node)["faceColor"]["alpha"] = face_color[3]; + (*node)["power"] = power; + (*node)["specularColor"]["red"] = specular_color[0]; + (*node)["specularColor"]["green"] = specular_color[1]; + (*node)["specularColor"]["blue"] = specular_color[2]; + (*node)["emissiveColor"]["red"] = emissive_color[0]; + (*node)["emissiveColor"]["green"] = emissive_color[1]; + (*node)["emissiveColor"]["blue"] = emissive_color[2]; + + return node; +} + +/** + * Creates a new TextureFilename instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_TextureFilename(const string &name, const Filename &filename) { + XFileTemplate *xtemplate = XFile::find_standard_template("TextureFilename"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + (*node)["filename"] = filename.to_os_specific(); + + return node; +} + +/** + * Creates a new Frame instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_Frame(const string &name) { + XFileTemplate *xtemplate = XFile::find_standard_template("Frame"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), name, xtemplate); + add_child(node); + node->zero_fill(); + + return node; +} + +/** + * Creates a new FrameTransformMatrix instance, as a child of this node. + */ +XFileDataNode *XFileNode:: +add_FrameTransformMatrix(const LMatrix4d &mat) { + XFileTemplate *xtemplate = + XFile::find_standard_template("FrameTransformMatrix"); + nassertr(xtemplate != nullptr, nullptr); + XFileDataNodeTemplate *node = + new XFileDataNodeTemplate(get_x_file(), "", xtemplate); + add_child(node); + node->zero_fill(); + + XFileDataObject &xmat = (*node)["frameMatrix"]["matrix"]; + xmat[0] = mat(0, 0); + xmat[1] = mat(0, 1); + xmat[2] = mat(0, 2); + xmat[3] = mat(0, 3); + + xmat[4] = mat(1, 0); + xmat[5] = mat(1, 1); + xmat[6] = mat(1, 2); + xmat[7] = mat(1, 3); + + xmat[8] = mat(2, 0); + xmat[9] = mat(2, 1); + xmat[10] = mat(2, 2); + xmat[11] = mat(2, 3); + + xmat[12] = mat(3, 0); + xmat[13] = mat(3, 1); + xmat[14] = mat(3, 2); + xmat[15] = mat(3, 3); + + return node; +} + +/** + * Transforms the indicated egg name to a name that is acceptable for a node + * in the X File format. + */ +string XFileNode:: +make_nice_name(const string &str) { + string result; + + string::const_iterator si; + for (si = str.begin(); si != str.end(); ++si) { + if (isalnum(*si)) { + result += (*si); + } else { + switch (*si) { + case '-': + result += (*si); + break; + default: + result += "_"; + } + } + } + + if (str.empty() || isdigit(str[0])) { + // If the name begins with a digit, or if it is empty, then we must make + // it begin with something else, like for instance an underscore. + result = '_' + result; + } + + return result; +} diff --git a/pandatool/src/xfile/xFileNode.h b/pandatool/src/xfile/xFileNode.h new file mode 100644 index 00000000..2cd9f6d3 --- /dev/null +++ b/pandatool/src/xfile/xFileNode.h @@ -0,0 +1,139 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileNode.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILENODE_H +#define XFILENODE_H + +#include "pandatoolbase.h" +#include "typedObject.h" +#include "referenceCount.h" +#include "pointerTo.h" +#include "namable.h" +#include "pnotify.h" +#include "pvector.h" +#include "pmap.h" +#include "luse.h" + +class XFile; +class WindowsGuid; +class XFileParseDataList; +class XFileDataDef; +class XFileDataObject; +class XFileDataNode; +class XFileDataNodeTemplate; +class Filename; + +/** + * A single node of an X file. This may be either a template or a data node. + */ +class XFileNode : public TypedObject, public Namable, + virtual public ReferenceCount { +protected: + INLINE XFileNode(XFile *x_file); + +public: + XFileNode(XFile *x_file, const std::string &name); + virtual ~XFileNode(); + + INLINE XFile *get_x_file() const; + + INLINE int get_num_children() const; + INLINE XFileNode *get_child(int n) const; + XFileNode *find_child(const std::string &name) const; + int find_child_index(const std::string &name) const; + int find_child_index(const XFileNode *child) const; + XFileNode *find_descendent(const std::string &name) const; + + INLINE int get_num_objects() const; + INLINE XFileDataNode *get_object(int n) const; + + virtual bool has_guid() const; + virtual const WindowsGuid &get_guid() const; + + virtual bool is_template_def() const; + virtual bool is_reference() const; + virtual bool is_object() const; + virtual bool is_standard_object(const std::string &template_name) const; + + void add_child(XFileNode *node); + virtual void clear(); + + virtual void write_text(std::ostream &out, int indent_level) const; + + typedef pmap PrevData; + + virtual bool repack_data(XFileDataObject *object, + const XFileParseDataList &parse_data_list, + PrevData &prev_data, + size_t &index, size_t &sub_index) const; + + virtual bool fill_zero_data(XFileDataObject *object) const; + + virtual bool matches(const XFileNode *other) const; + + // The following methods can be used to create instances of the standard + // template objects. These definitions match those defined in + // standardTemplates.x in this directory (and compiled into the executable). + XFileDataNode *add_Mesh(const std::string &name); + XFileDataNode *add_MeshNormals(const std::string &name); + XFileDataNode *add_MeshVertexColors(const std::string &name); + XFileDataNode *add_MeshTextureCoords(const std::string &name); + XFileDataNode *add_MeshMaterialList(const std::string &name); + XFileDataNode *add_Material(const std::string &name, const LColor &face_color, + double power, const LRGBColor &specular_color, + const LRGBColor &emissive_color); + XFileDataNode *add_TextureFilename(const std::string &name, + const Filename &filename); + XFileDataNode *add_Frame(const std::string &name); + XFileDataNode *add_FrameTransformMatrix(const LMatrix4d &mat); + +public: + static std::string make_nice_name(const std::string &str); + +protected: + XFile *_x_file; + + typedef pvector< PT(XFileNode) > Children; + Children _children; + + typedef pvector Objects; + Objects _objects; + + typedef pmap ChildrenByName; + ChildrenByName _children_by_name; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedObject::init_type(); + ReferenceCount::init_type(); + register_type(_type_handle, "XFileNode", + TypedObject::get_class_type(), + ReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class XFileDataNodeReference; +}; + +#include "xFileNode.I" + +#endif diff --git a/pandatool/src/xfile/xFileParseData.I b/pandatool/src/xfile/xFileParseData.I new file mode 100644 index 00000000..64199737 --- /dev/null +++ b/pandatool/src/xfile/xFileParseData.I @@ -0,0 +1,12 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileParseData.I + * @author drose + * @date 2004-10-07 + */ diff --git a/pandatool/src/xfile/xFileParseData.cxx b/pandatool/src/xfile/xFileParseData.cxx new file mode 100644 index 00000000..0fcd8dbe --- /dev/null +++ b/pandatool/src/xfile/xFileParseData.cxx @@ -0,0 +1,39 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileParseData.cxx + * @author drose + * @date 2004-10-07 + */ + +#include "xFileParseData.h" +#include "xLexerDefs.h" + + +/** + * + */ +XFileParseData:: +XFileParseData() : + _parse_flags(0) +{ + // Save the line number, column number, and line text in case we detect an + // error later and want to report a meaningful message to the user. + _line_number = x_line_number; + _col_number = x_col_number; + _current_line = x_current_line; +} + +/** + * Reports a parsing error message to the user, showing the line and column + * from which this object was originally parsed. + */ +void XFileParseData:: +yyerror(const std::string &message) const { + xyyerror(message, _line_number, _col_number, _current_line); +} diff --git a/pandatool/src/xfile/xFileParseData.h b/pandatool/src/xfile/xFileParseData.h new file mode 100644 index 00000000..2e7ef8db --- /dev/null +++ b/pandatool/src/xfile/xFileParseData.h @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileParseData.h + * @author drose + * @date 2004-10-07 + */ + +#ifndef XFILEPARSEDATA_H +#define XFILEPARSEDATA_H + +#include "pandatoolbase.h" +#include "xFileDataObject.h" +#include "pointerTo.h" +#include "pta_int.h" +#include "pta_double.h" +#include "pvector.h" + +/** + * This class is used to fill up the data into an XFileDataNodeTemplate object + * as the data values are parsed out of the X file. It only has a temporary + * lifespan; it will be converted into actual data by + * XFileDataNodeTemplate::finalize_parse_data(). + */ +class XFileParseData { +public: + XFileParseData(); + + void yyerror(const std::string &message) const; + + enum ParseFlags { + PF_object = 0x001, + PF_reference = 0x002, + PF_double = 0x004, + PF_int = 0x008, + PF_string = 0x010, + PF_any_data = 0x01f, + }; + + PT(XFileDataObject) _object; + PTA_double _double_list; + PTA_int _int_list; + std::string _string; + int _parse_flags; + + int _line_number; + int _col_number; + std::string _current_line; +}; + +/** + * A container for a pvector of the above objects. We need this wrapper class + * to avoid circular #includes; this allows XFileNode to define a forward + * reference to this class (without having to include this file or know that + * it contains a template class). + */ +class XFileParseDataList { +public: + typedef pvector List; + List _list; +}; + +#include "xFileParseData.I" + +#endif diff --git a/pandatool/src/xfile/xFileTemplate.I b/pandatool/src/xfile/xFileTemplate.I new file mode 100644 index 00000000..d56ff289 --- /dev/null +++ b/pandatool/src/xfile/xFileTemplate.I @@ -0,0 +1,70 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileTemplate.I + * @author drose + * @date 2004-10-03 + */ + +/** + * Returns true if this particular template is one of the "standard" templates + * defined by standardTemplates.x in this directory (and compiled into the + * binary), or false if it is a user-custom template. + */ +INLINE bool XFileTemplate:: +is_standard() const { + return _is_standard; +} + +/** + * Sets whether the template is considered "open" or not. If it is open (this + * flag is true), the set of options is ignored and the instances of this + * template may include any types of children. If it is closed (false), only + * the named types may be added. + */ +INLINE void XFileTemplate:: +set_open(bool open) { + _open = open; +} + +/** + * Returns whether the template is considered "open" or not. If it is open + * (this flag is true), the set of options is ignored and the instances of + * this template may include any types of children. If it is closed (false), + * only the named types may be added. + */ +INLINE bool XFileTemplate:: +get_open() const { + return _open; +} + +/** + * Adds a new type to the list of allowable types of child nodes for an + * instance of this template. + */ +INLINE void XFileTemplate:: +add_option(XFileTemplate *option) { + _options.push_back(option); +} + +/** + * Returns the number of templates on the options list. + */ +INLINE int XFileTemplate:: +get_num_options() const { + return _options.size(); +} + +/** + * Returns the nth template on the options list. + */ +INLINE XFileTemplate *XFileTemplate:: +get_option(int n) const { + nassertr(n >= 0 && n < (int)_options.size(), nullptr); + return _options[n]; +} diff --git a/pandatool/src/xfile/xFileTemplate.cxx b/pandatool/src/xfile/xFileTemplate.cxx new file mode 100644 index 00000000..e4ef5e2c --- /dev/null +++ b/pandatool/src/xfile/xFileTemplate.cxx @@ -0,0 +1,131 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileTemplate.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileTemplate.h" +#include "indent.h" + +TypeHandle XFileTemplate::_type_handle; + +/** + * + */ +XFileTemplate:: +XFileTemplate(XFile *x_file, const std::string &name, const WindowsGuid &guid) : + XFileNode(x_file, name), + _guid(guid), + _is_standard(false), + _open(false) +{ +} + +/** + * + */ +XFileTemplate:: +~XFileTemplate() { + clear(); +} + +/** + * Returns true if this node has a GUID associated. + */ +bool XFileTemplate:: +has_guid() const { + return true; +} + +/** + * Returns the GUID associated with this template. + */ +const WindowsGuid &XFileTemplate:: +get_guid() const { + return _guid; +} + +/** + * Returns true if this node represents the definition of some template. This + * is the template definition, not an actual data object that represents an + * instance of the template. If the file strictly uses standard templates, + * the presence of template definitions is optional. + * + * If this returns true, the node must be of type XFileTemplate. + */ +bool XFileTemplate:: +is_template_def() const { + return true; +} + +/** + * Removes all children from the node, and otherwise resets it to its initial + * state. + */ +void XFileTemplate:: +clear() { + XFileNode::clear(); + _options.clear(); +} + +/** + * Writes a suitable representation of this node to an .x file in text mode. + */ +void XFileTemplate:: +write_text(std::ostream &out, int indent_level) const { + indent(out, indent_level) + << "template " << get_name() << " {\n"; + indent(out, indent_level + 2) + << "<" << _guid << ">\n"; + + XFileNode::write_text(out, indent_level + 2); + + if (get_open()) { + // An open template + indent(out, indent_level + 2) + << "[ ... ]\n"; + + } else if (!_options.empty()) { + // A restricted template + indent(out, indent_level + 2); + + char delimiter = '['; + Options::const_iterator ri; + for (ri = _options.begin(); ri != _options.end(); ++ri) { + XFileTemplate *option = (*ri); + out << delimiter << " " + << option->get_name() << " <" << option->get_guid() + << ">"; + delimiter = ','; + } + out << " ]\n"; + } + + indent(out, indent_level) + << "}\n"; +} + +/** + * Returns true if the node, particularly a template node, is structurally + * equivalent to the other node (which must be of the same type). This checks + * data element types, but does not compare data element names. + */ +bool XFileTemplate:: +matches(const XFileNode *other) const { + if (!XFileNode::matches(other)) { + return false; + } + + // We *could* compare the openclosedoptions associated with the template, + // but since this is only used for validating the set of children for the + // instances of this template (which we don't even bother to do anyway), it + // doesn't seem to matter. + return true; +} diff --git a/pandatool/src/xfile/xFileTemplate.h b/pandatool/src/xfile/xFileTemplate.h new file mode 100644 index 00000000..d119a297 --- /dev/null +++ b/pandatool/src/xfile/xFileTemplate.h @@ -0,0 +1,81 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileTemplate.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILETEMPLATE_H +#define XFILETEMPLATE_H + +#include "pandatoolbase.h" +#include "xFileNode.h" +#include "windowsGuid.h" + +class XFileDataDef; + +/** + * A template definition in the X file. This defines the data structures that + * may be subsequently read. + */ +class XFileTemplate : public XFileNode { +public: + XFileTemplate(XFile *x_file, const std::string &name, const WindowsGuid &guid); + virtual ~XFileTemplate(); + + virtual bool has_guid() const; + virtual const WindowsGuid &get_guid() const; + + virtual bool is_template_def() const; + + virtual void clear() final; + virtual void write_text(std::ostream &out, int indent_level) const; + + INLINE bool is_standard() const; + + INLINE void set_open(bool open); + INLINE bool get_open() const; + + INLINE void add_option(XFileTemplate *option); + INLINE int get_num_options() const; + INLINE XFileTemplate *get_option(int n) const; + + virtual bool matches(const XFileNode *other) const; + +private: + WindowsGuid _guid; + bool _is_standard; + bool _open; + + typedef pvector< PT(XFileTemplate) > Options; + Options _options; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + XFileNode::init_type(); + register_type(_type_handle, "XFileTemplate", + XFileNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class XFile; +}; + +#include "xFileTemplate.I" + +#endif diff --git a/pandatool/src/xfile/xLexer.cxx.prebuilt b/pandatool/src/xfile/xLexer.cxx.prebuilt new file mode 100644 index 00000000..92eb941a --- /dev/null +++ b/pandatool/src/xfile/xLexer.cxx.prebuilt @@ -0,0 +1,2637 @@ +#line 2 "lex.yy.c" + +#line 4 "lex.yy.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer xyy_create_buffer +#define yy_delete_buffer xyy_delete_buffer +#define yy_flex_debug xyy_flex_debug +#define yy_init_buffer xyy_init_buffer +#define yy_flush_buffer xyy_flush_buffer +#define yy_load_buffer_state xyy_load_buffer_state +#define yy_switch_to_buffer xyy_switch_to_buffer +#define yyin xyyin +#define yyleng xyyleng +#define yylex xyylex +#define yylineno xyylineno +#define yyout xyyout +#define yyrestart xyyrestart +#define yytext xyytext +#define yywrap xyywrap +#define yyalloc xyyalloc +#define yyrealloc xyyrealloc +#define yyfree xyyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 35 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +/* C99 requires __STDC__ to be defined as 1. */ +#if defined (__STDC__) + +#define YY_USE_CONST + +#endif /* defined (__STDC__) */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE xyyrestart(xyyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int xyyleng; + +extern FILE *xyyin, *xyyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up xyytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up xyytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via xyyrestart()), so that the user can continue scanning by + * just pointing xyyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when xyytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int xyyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow xyywrap()'s to do buffer switches + * instead of setting up a fresh xyyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void xyyrestart (FILE *input_file ); +void xyy_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE xyy_create_buffer (FILE *file,int size ); +void xyy_delete_buffer (YY_BUFFER_STATE b ); +void xyy_flush_buffer (YY_BUFFER_STATE b ); +void xyypush_buffer_state (YY_BUFFER_STATE new_buffer ); +void xyypop_buffer_state (void ); + +static void xyyensure_buffer_stack (void ); +static void xyy_load_buffer_state (void ); +static void xyy_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER xyy_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE xyy_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE xyy_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE xyy_scan_bytes (yyconst char *bytes,int len ); + +void *xyyalloc (yy_size_t ); +void *xyyrealloc (void *,yy_size_t ); +void xyyfree (void * ); + +#define yy_new_buffer xyy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + xyyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + xyy_create_buffer(xyyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + xyyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + xyy_create_buffer(xyyin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +typedef unsigned char YY_CHAR; + +FILE *xyyin = (FILE *) 0, *xyyout = (FILE *) 0; + +typedef int yy_state_type; + +extern int xyylineno; + +int xyylineno = 1; + +extern char *xyytext; +#define yytext_ptr xyytext + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up xyytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + xyyleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 35 +#define YY_END_OF_BUFFER 36 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_acclist[200] = + { 0, + 36, 34, 35, 2, 34, 35, 1, 35, 30, 34, + 35, 4, 34, 35, 32, 34, 35, 34, 35, 10, + 34, 35, 32, 34, 35, 9, 34, 35, 34, 35, + 26, 34, 35, 11, 34, 35, 31, 34, 35, 32, + 34, 35, 32, 34, 35, 32, 34, 35, 32, 34, + 35, 32, 34, 35, 32, 34, 35, 32, 34, 35, + 32, 34, 35, 32, 34, 35, 7, 34, 35, 8, + 34, 35, 5, 34, 35, 6, 34, 35, 1, 4, + 32, 26, 32, 33, 32, 33, 32, 26, 32, 3, + 27,16412, 26, 27,16412, 33, 33, 26, 32, 32, + + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 33, 32, 33, 32, 33, 32, 33, + 32, 32, 29, 3, 27, 33, 33, 33, 33, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 8220, 32, 13, 32, 14, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 25, 32, 32, 32, 29, 29, 12, 32, 32, 32, + 17, 32, 19, 32, 32, 32, 24, 32, 32, 22, + 32, 32, 32, 16, 32, 18, 32, 20, 32, 32, + 32, 29, 15, 32, 32, 23, 32, 21, 32 + + } ; + +static yyconst flex_int16_t yy_accept[142] = + { 0, + 1, 1, 1, 2, 4, 7, 9, 12, 15, 18, + 20, 23, 26, 29, 31, 34, 37, 40, 43, 46, + 49, 52, 55, 58, 61, 64, 67, 70, 73, 76, + 79, 80, 81, 82, 82, 83, 85, 87, 88, 90, + 90, 91, 93, 96, 97, 98, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 115, 117, 119, 121, 122, 123, 124, 124, + 125, 125, 126, 127, 128, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 145, 145, 145, 145, 145, 146, 147, 149, + + 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 163, 164, 165, 166, 167, 169, 170, 171, 173, + 175, 176, 177, 179, 180, 182, 183, 183, 184, 186, + 188, 190, 191, 192, 192, 193, 195, 196, 198, 200, + 200 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 1, 6, 7, 1, 1, 1, 1, 8, + 8, 1, 9, 10, 11, 12, 13, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 1, 15, 16, + 1, 1, 1, 1, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 8, 8, 26, 27, 28, 29, 30, + 8, 31, 32, 33, 34, 8, 35, 8, 36, 8, + 37, 1, 38, 1, 8, 1, 39, 40, 41, 42, + + 43, 44, 45, 46, 47, 8, 8, 48, 49, 50, + 51, 52, 8, 53, 54, 55, 56, 8, 57, 8, + 58, 8, 59, 1, 60, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static yyconst flex_int32_t yy_meta[61] = + { 0, + 1, 2, 3, 1, 2, 1, 1, 4, 1, 2, + 4, 4, 1, 4, 2, 1, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 1, 1, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 1, 1 + } ; + +static yyconst flex_int16_t yy_base[151] = + { 0, + 0, 0, 336, 370, 370, 0, 370, 0, 0, 49, + 370, 53, 320, 290, 64, 370, 370, 31, 32, 48, + 42, 44, 53, 60, 63, 54, 370, 370, 370, 370, + 0, 0, 0, 248, 104, 0, 86, 215, 110, 121, + 0, 135, 146, 0, 116, 157, 171, 54, 56, 100, + 99, 107, 114, 118, 117, 122, 129, 139, 144, 145, + 146, 0, 0, 182, 190, 193, 207, 228, 202, 0, + 201, 239, 0, 0, 212, 220, 167, 176, 174, 194, + 217, 214, 229, 219, 227, 224, 226, 241, 240, 240, + 252, 175, 196, 206, 177, 274, 370, 228, 0, 0, + + 240, 243, 251, 239, 242, 247, 263, 259, 259, 263, + 0, 165, 294, 296, 311, 0, 272, 281, 0, 0, + 295, 295, 0, 302, 0, 300, 319, 300, 0, 0, + 0, 294, 308, 124, 333, 0, 310, 0, 0, 370, + 354, 358, 130, 129, 361, 125, 97, 365, 92, 83 + } ; + +static yyconst flex_int16_t yy_def[151] = + { 0, + 140, 1, 140, 140, 140, 141, 140, 142, 143, 140, + 140, 144, 140, 140, 145, 140, 140, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 140, 140, 140, 140, + 141, 142, 143, 140, 140, 146, 147, 143, 144, 140, + 148, 140, 140, 149, 150, 140, 145, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 146, 146, 147, 147, 143, 143, 140, 140, 148, + 140, 140, 149, 149, 150, 150, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 140, 140, 140, 140, 140, 140, 143, 143, 143, + + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 140, 140, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 140, 143, 143, 143, + 143, 143, 143, 140, 140, 143, 143, 143, 143, 0, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140 + } ; + +static yyconst flex_int16_t yy_nxt[431] = + { 0, + 4, 5, 6, 5, 5, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 9, 22, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 23, 24, 25, 26, 9, 27, 28, 18, 19, + 20, 21, 9, 22, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 23, 24, 25, 26, 9, 29, 30, + 34, 48, 35, 37, 38, 42, 39, 49, 43, 54, + 52, 50, 55, 42, 45, 46, 53, 47, 42, 51, + 58, 59, 61, 48, 77, 56, 74, 57, 78, 49, + 60, 54, 52, 50, 55, 73, 64, 62, 53, 65, + + 63, 51, 58, 59, 61, 42, 77, 56, 43, 57, + 78, 42, 60, 42, 43, 46, 79, 35, 42, 42, + 37, 67, 68, 39, 42, 68, 75, 73, 62, 76, + 68, 80, 36, 33, 40, 68, 42, 135, 79, 42, + 81, 69, 82, 71, 42, 71, 83, 42, 72, 42, + 43, 84, 85, 80, 71, 42, 71, 86, 68, 72, + 42, 68, 81, 69, 82, 87, 68, 88, 83, 89, + 40, 68, 42, 84, 85, 43, 90, 69, 113, 86, + 42, 45, 46, 98, 47, 42, 93, 87, 94, 88, + 96, 89, 64, 62, 68, 65, 99, 68, 90, 69, + + 64, 62, 68, 65, 100, 98, 66, 68, 68, 114, + 95, 68, 95, 91, 72, 96, 68, 115, 99, 94, + 66, 68, 75, 73, 101, 76, 100, 91, 66, 68, + 75, 73, 68, 76, 102, 91, 92, 68, 92, 93, + 42, 94, 68, 42, 103, 104, 101, 105, 42, 91, + 97, 106, 72, 42, 107, 108, 102, 109, 110, 111, + 95, 40, 112, 116, 117, 113, 103, 104, 118, 105, + 119, 120, 121, 106, 122, 68, 107, 108, 68, 109, + 110, 111, 123, 68, 124, 116, 117, 96, 68, 125, + 118, 126, 119, 120, 121, 68, 122, 68, 68, 128, + + 68, 129, 41, 68, 123, 68, 124, 113, 68, 114, + 68, 125, 68, 126, 130, 68, 127, 131, 132, 133, + 68, 128, 136, 129, 114, 68, 137, 134, 138, 134, + 139, 127, 135, 40, 68, 140, 130, 68, 127, 131, + 132, 133, 68, 140, 136, 140, 135, 68, 137, 140, + 138, 140, 139, 127, 31, 31, 140, 31, 32, 32, + 140, 32, 44, 140, 44, 70, 70, 140, 70, 3, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140 + } ; + +static yyconst flex_int16_t yy_chk[431] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 10, 18, 10, 12, 12, 15, 12, 19, 15, 22, + 21, 20, 23, 15, 15, 15, 21, 15, 15, 20, + 24, 25, 26, 18, 48, 23, 150, 23, 49, 19, + 25, 22, 21, 20, 23, 149, 37, 37, 21, 37, + + 147, 20, 24, 25, 26, 35, 48, 23, 35, 23, + 49, 39, 25, 35, 39, 35, 50, 35, 35, 39, + 39, 39, 40, 39, 39, 40, 45, 45, 146, 45, + 40, 51, 144, 143, 40, 40, 42, 134, 50, 42, + 52, 40, 53, 42, 42, 42, 54, 43, 42, 42, + 43, 55, 56, 51, 43, 43, 43, 57, 46, 43, + 43, 46, 52, 40, 53, 58, 46, 59, 54, 60, + 46, 46, 47, 55, 56, 47, 61, 46, 112, 57, + 47, 47, 47, 77, 47, 47, 92, 58, 92, 59, + 95, 60, 64, 64, 66, 64, 78, 66, 61, 46, + + 65, 65, 66, 65, 79, 77, 66, 66, 67, 93, + 69, 67, 69, 66, 71, 69, 67, 94, 78, 94, + 67, 67, 75, 75, 80, 75, 79, 67, 38, 68, + 76, 76, 68, 76, 81, 66, 68, 68, 68, 68, + 72, 68, 68, 72, 82, 83, 80, 84, 72, 67, + 72, 85, 72, 72, 86, 87, 81, 88, 89, 90, + 91, 34, 91, 98, 101, 91, 82, 83, 102, 84, + 103, 104, 105, 85, 106, 96, 86, 87, 96, 88, + 89, 90, 107, 96, 108, 98, 101, 96, 96, 109, + 102, 110, 103, 104, 105, 113, 106, 114, 113, 117, + + 114, 118, 14, 113, 107, 114, 108, 113, 113, 114, + 114, 109, 115, 110, 121, 115, 114, 122, 124, 126, + 115, 117, 128, 118, 115, 115, 132, 127, 133, 127, + 137, 115, 127, 13, 135, 3, 121, 135, 114, 122, + 124, 126, 135, 0, 128, 0, 135, 135, 132, 0, + 133, 0, 137, 115, 141, 141, 0, 141, 142, 142, + 0, 142, 145, 0, 145, 148, 148, 0, 148, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 140, 140, 140, 140, 140 + } ; + +extern int xyy_flex_debug; +int xyy_flex_debug = 0; + +static yy_state_type *yy_state_buf=0, *yy_state_ptr=0; +static char *yy_full_match; +static int yy_lp; +static int yy_looking_for_trail_begin = 0; +static int yy_full_lp; +static int *yy_full_state; +#define YY_TRAILING_MASK 0x2000 +#define YY_TRAILING_HEAD_MASK 0x4000 +#define REJECT \ +{ \ +*yy_cp = (yy_hold_char); /* undo effects of setting up xyytext */ \ +yy_cp = (yy_full_match); /* restore poss. backed-over text */ \ +(yy_lp) = (yy_full_lp); /* restore orig. accepting pos. */ \ +(yy_state_ptr) = (yy_full_state); /* restore orig. state */ \ +yy_current_state = *(yy_state_ptr); /* restore curr. state */ \ +++(yy_lp); \ +goto find_rule; \ +} + +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *xyytext; +#line 1 "xLexer.lxx" +/** + * @file xLexer.lxx + * @author drose + * @date 2004-10-03 + */ +#line 9 "xLexer.lxx" +#include "xLexerDefs.h" +#include "xParserDefs.h" +#include "xParser.h" +#include "indent.h" +#include "string_utils.h" +#include "config_xfile.h" + +static int yyinput(void); // declared by flex. +extern "C" int xyywrap(); + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number and col_number as we parse the file, so +// that we can report the position of an error. +int x_line_number = 0; +int x_col_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +char x_current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static std::istream *input_p = nullptr; + +// This is the name of the x file we're parsing. We keep it so we +// can print it out for error messages. +static std::string x_filename; + + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the lexer. +//////////////////////////////////////////////////////////////////// + +void +x_init_lexer(std::istream &in, const std::string &filename) { + input_p = ∈ + x_filename = filename; + x_line_number = 0; + x_col_number = 0; + error_count = 0; + warning_count = 0; +} + +int +x_error_count() { + return error_count; +} + +int +x_warning_count() { + return warning_count; +} + + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +xyywrap(void) { + return 1; +} + +void +xyyerror(const std::string &msg) { + xyyerror(msg, x_line_number, x_col_number, x_current_line); +} + +void +xyyerror(const std::string &msg, int line_number, int col_number, + const std::string ¤t_line) { + xfile_cat.error(false) << "\nError"; + if (!x_filename.empty()) { + xfile_cat.error(false) << " in " << x_filename; + } + xfile_cat.error(false) + << " at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(xfile_cat.error(false), col_number-1) + << "^\n" << msg << "\n\n"; + + error_count++; +} + +void +xyywarning(const std::string &msg) { + xfile_cat.warning(false) << "\nWarning"; + if (!x_filename.empty()) { + xfile_cat.warning(false) << " in " << x_filename; + } + xfile_cat.warning(false) + << " at line " << x_line_number << ", column " << x_col_number << ":\n" + << x_current_line << "\n"; + indent(xfile_cat.warning(false), x_col_number-1) + << "^\n" << msg << "\n\n"; + + warning_count++; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + nassertv(input_p != NULL); + if (*input_p) { + input_p->read(buffer, max_size); + result = input_p->gcount(); + if (result >= 0 && result < max_size) { + // Truncate at the end of the read. + buffer[result] = '\0'; + } + + if (x_line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the x_current_line array. This + // is because the \n.* rule below, which fills x_current_line + // normally, doesn't catch the first line. + strncpy(x_current_line, xyytext, max_error_width); + x_current_line[max_error_width] = '\0'; + x_line_number++; + x_col_number = 0; + + // Truncate it at the newline. + char *end = strchr(x_current_line, '\n'); + if (end != NULL) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT + +// Define this macro carefully, since different flex versions call it +// with a different type for result. +#define YY_INPUT(buffer, result, max_size) { \ + int int_result = 0; \ + input_chars((buffer), int_result, (max_size)); \ + (result) = int_result; \ +} + +// read_char reads and returns a single character, incrementing the +// supplied line and column numbers as appropriate. A convenience +// function for the scanning functions below. +static int +read_char(int &line, int &col) { + int c = yyinput(); + if (c == '\n') { + line++; + col = 0; + } else { + col++; + } + return c; +} + +// scan_quoted_string reads a string delimited by quotation marks and +// returns it. +static std::string +scan_quoted_string(char quote_mark) { + std::string result; + + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global x_line_number and x_col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = x_line_number; + int col = x_col_number; + + int c; + c = read_char(line, col); + while (c != quote_mark && c != EOF) { + // A newline is not allowed within a string unless it is escaped. + if (c == '\n') { + c = EOF; + break; + + } else if (c == '\\') { + // Backslash escapes the following character. We also respect + // some C conventions. + c = read_char(line, col); + switch (c) { + case 'a': + result += '\a'; + c = read_char(line, col); + break; + + case 'n': + result += '\n'; + c = read_char(line, col); + break; + + case 'r': + result += '\r'; + c = read_char(line, col); + break; + + case 't': + result += '\t'; + c = read_char(line, col); + break; + + case 'x': + { + int hex = 0; + c = read_char(line, col); + for (int i = 0; i < 2 && isxdigit(c); i++) { + hex = hex * 16 + (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10); + c = read_char(line, col); + } + + result += hex; + } + break; + + case '0': + { + int oct = 0; + c = read_char(line, col); + for (int i = 0; i < 3 && (c >= '0' && c < '7'); i++) { + oct = oct * 8 + (c - '0'); + c = read_char(line, col); + } + + result += oct; + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int dec = 0; + c = read_char(line, col); + for (int i = 0; i < 3 && isdigit(c); i++) { + dec = dec * 10 + (c - '0'); + c = read_char(line, col); + } + + result += dec; + } + break; + + case EOF: + break; + + default: + result += c; + c = read_char(line, col); + } + + } else { + result += c; + c = read_char(line, col); + } + } + + if (c == EOF) { + xyyerror("This quotation mark is unterminated."); + } + + x_line_number = line; + x_col_number = col; + + return result; +} + +// scan_guid_string reads a string of hexadecimal digits delimited by +// angle brackets and returns the corresponding string. +static std::string +scan_guid_string() { + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global x_line_number and x_col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = x_line_number; + int col = x_col_number; + + int num_digits = 0; + int num_hyphens = 0; + + std::string result; + + int c; + c = read_char(line, col); + while (c != '>' && c != EOF) { + if (isxdigit(c)) { + num_digits++; + + } else if (c == '-') { + num_hyphens++; + + } else { + x_line_number = line; + x_col_number = col; + xyyerror("Invalid character in GUID."); + return std::string(); + } + + result += c; + + c = read_char(line, col); + } + + if (c == EOF) { + xyyerror("This GUID string is unterminated."); + return std::string(); + + } else if (num_digits != 32) { + xyyerror("Incorrect number of hex digits in GUID."); + return std::string(); + + } else if (num_hyphens != 4) { + xyyerror("Incorrect number of hyphens in GUID."); + return std::string(); + } + + x_line_number = line; + x_col_number = col; + + return result; +} + +// Parses the text into a list of integers and returns them. +static PTA_int +scan_int_list(const std::string &text) { + PTA_int result; + + vector_string words; + tokenize(text, words, ",;"); + + vector_string::const_iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + std::string trimmed = trim(*wi); + if (!trimmed.empty()) { + int number = 0; + string_to_int(trimmed, number); + result.push_back(number); + } + } + + return result; +} + +// Parses the text into a list of doubles and returns them. +static PTA_double +scan_double_list(const std::string &text) { + PTA_double result; + + vector_string words; + tokenize(text, words, ",;"); + + vector_string::const_iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + std::string trimmed = trim(*wi); + if (!trimmed.empty()) { + double number = 0.0; + string_to_double(trimmed, number); + result.push_back(number); + } + } + + return result; +} + + + +// accept() is called below as each piece is pulled off and +// accepted by the lexer; it increments the current column number. +inline void accept() { + x_col_number += xyyleng; +} + +#line 1052 "lex.yy.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals (void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int xyylex_destroy (void ); + +int xyyget_debug (void ); + +void xyyset_debug (int debug_flag ); + +YY_EXTRA_TYPE xyyget_extra (void ); + +void xyyset_extra (YY_EXTRA_TYPE user_defined ); + +FILE *xyyget_in (void ); + +void xyyset_in (FILE * in_str ); + +FILE *xyyget_out (void ); + +void xyyset_out (FILE * out_str ); + +int xyyget_leng (void ); + +char *xyyget_text (void ); + +int xyyget_lineno (void ); + +void xyyset_lineno (int line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int xyywrap (void ); +#else +extern int xyywrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO fwrite( xyytext, xyyleng, 1, xyyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( xyyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( xyyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, xyyin))==0 && ferror(xyyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(xyyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int xyylex (void); + +#define YY_DECL int xyylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after xyytext and xyyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 414 "xLexer.lxx" + + + + + +#line 1240 "lex.yy.c" + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + /* Create the reject buffer large enough to save one state per allowed character. */ + if ( ! (yy_state_buf) ) + (yy_state_buf) = (yy_state_type *)xyyalloc(YY_STATE_BUF_SIZE ); + if ( ! (yy_state_buf) ) + YY_FATAL_ERROR( "out of dynamic memory in xyylex()" ); + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! xyyin ) + xyyin = stdin; + + if ( ! xyyout ) + xyyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + xyyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + xyy_create_buffer(xyyin,YY_BUF_SIZE ); + } + + xyy_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of xyytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); + + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 141 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; + ++yy_cp; + } + while ( yy_base[yy_current_state] != 370 ); + +yy_find_action: + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; +find_rule: /* we branch to this label when backing up */ + for ( ; ; ) /* until we find what rule we matched */ + { + if ( (yy_lp) && (yy_lp) < yy_accept[yy_current_state + 1] ) + { + yy_act = yy_acclist[(yy_lp)]; + if ( yy_act & YY_TRAILING_HEAD_MASK || + (yy_looking_for_trail_begin) ) + { + if ( yy_act == (yy_looking_for_trail_begin) ) + { + (yy_looking_for_trail_begin) = 0; + yy_act &= ~YY_TRAILING_HEAD_MASK; + break; + } + } + else if ( yy_act & YY_TRAILING_MASK ) + { + (yy_looking_for_trail_begin) = yy_act & ~YY_TRAILING_MASK; + (yy_looking_for_trail_begin) |= YY_TRAILING_HEAD_MASK; + } + else + { + (yy_full_match) = yy_cp; + (yy_full_state) = (yy_state_ptr); + (yy_full_lp) = (yy_lp); + break; + } + ++(yy_lp); + goto find_rule; + } + --yy_cp; + yy_current_state = *--(yy_state_ptr); + (yy_lp) = yy_accept[yy_current_state]; + } + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 419 "xLexer.lxx" +{ + // New line. Save a copy of the line so we can print it out for the + // benefit of the user in case we get an error. + + strncpy(x_current_line, xyytext+1, max_error_width); + x_current_line[max_error_width] = '\0'; + x_line_number++; + x_col_number=0; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + YY_BREAK +case 2: +YY_RULE_SETUP +#line 433 "xLexer.lxx" +{ + // Eat whitespace. + accept(); +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 438 "xLexer.lxx" +{ + // Eat C++-style comments. + accept(); +} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 443 "xLexer.lxx" +{ + // Eat sh-style comments. + accept(); +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 448 "xLexer.lxx" +{ + accept(); + return TOKEN_OBRACE; +} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 453 "xLexer.lxx" +{ + accept(); + return TOKEN_CBRACE; +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 458 "xLexer.lxx" +{ + accept(); + return TOKEN_OBRACKET; +} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 463 "xLexer.lxx" +{ + accept(); + return TOKEN_CBRACKET; +} + YY_BREAK +case 9: +YY_RULE_SETUP +#line 468 "xLexer.lxx" +{ + accept(); + return TOKEN_DOT; +} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 473 "xLexer.lxx" +{ + accept(); + return TOKEN_COMMA; +} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 478 "xLexer.lxx" +{ + accept(); + return TOKEN_SEMICOLON; +} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 483 "xLexer.lxx" +{ + accept(); + return TOKEN_ARRAY; +} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 488 "xLexer.lxx" +{ + accept(); + return TOKEN_UCHAR; +} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 493 "xLexer.lxx" +{ + accept(); + return TOKEN_CHAR; +} + YY_BREAK +case 15: +YY_RULE_SETUP +#line 498 "xLexer.lxx" +{ + accept(); + return TOKEN_CSTRING; +} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 503 "xLexer.lxx" +{ + accept(); + return TOKEN_DOUBLE; +} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 508 "xLexer.lxx" +{ + accept(); + return TOKEN_DWORD; +} + YY_BREAK +case 18: +YY_RULE_SETUP +#line 513 "xLexer.lxx" +{ + accept(); + return TOKEN_SDWORD; +} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 518 "xLexer.lxx" +{ + accept(); + return TOKEN_FLOAT; +} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 523 "xLexer.lxx" +{ + accept(); + return TOKEN_LPSTR; +} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 528 "xLexer.lxx" +{ + accept(); + return TOKEN_TEMPLATE; +} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 533 "xLexer.lxx" +{ + accept(); + return TOKEN_UCHAR; +} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 538 "xLexer.lxx" +{ + accept(); + return TOKEN_UNICODE; +} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 543 "xLexer.lxx" +{ + accept(); + return TOKEN_SWORD; +} + YY_BREAK +case 25: +YY_RULE_SETUP +#line 548 "xLexer.lxx" +{ + accept(); + return TOKEN_WORD; +} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 553 "xLexer.lxx" +{ + // A signed or unsigned integer number. + accept(); + xyylval.u.number = atol(xyytext); + xyylval.str = trim_right(xyytext); + + return TOKEN_INTEGER; +} + YY_BREAK +case 27: +YY_RULE_SETUP +#line 562 "xLexer.lxx" +{ + // An integer as part of a semicolon- or comma-delimited list. + accept(); + xyylval.int_list = scan_int_list(xyytext); + + return TOKEN_INTEGER_LIST; +} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 570 "xLexer.lxx" +{ + // This rule is used to match an integer list that is followed by a + // floating-point number. It's designed to prevent "0;0.5" from + // being interpreted as "0;0" followed by ".5". + accept(); + xyylval.int_list = scan_int_list(xyytext); + + return TOKEN_INTEGER_LIST; +} + YY_BREAK +case 29: +YY_RULE_SETUP +#line 580 "xLexer.lxx" +{ + // A floating-point number as part of a semicolon- or comma-delimited list. + accept(); + xyylval.double_list = scan_double_list(xyytext); + + return TOKEN_REALNUM_LIST; +} + YY_BREAK +case 30: +YY_RULE_SETUP +#line 588 "xLexer.lxx" +{ + // Quoted string. + accept(); + xyylval.str = scan_quoted_string('"'); + return TOKEN_STRING; +} + YY_BREAK +case 31: +YY_RULE_SETUP +#line 595 "xLexer.lxx" +{ + // Long GUID string. + accept(); + xyylval.str = scan_guid_string(); + + if (!xyylval.guid.parse_string(xyylval.str)) { + xyyerror("Malformed GUID."); + } + + return TOKEN_GUID; +} + YY_BREAK +case 32: +YY_RULE_SETUP +#line 607 "xLexer.lxx" +{ + // Identifier. + accept(); + xyylval.str = xyytext; + return TOKEN_NAME; +} + YY_BREAK +case 33: +YY_RULE_SETUP +#line 614 "xLexer.lxx" +{ + // Identifier with leading digit. + accept(); + xyylval.str = xyytext; + return TOKEN_NAME; +} + YY_BREAK +case 34: +YY_RULE_SETUP +#line 621 "xLexer.lxx" +{ + // Any other character is invalid. + accept(); + xyyerror("Invalid character '" + std::string(xyytext) + "'."); +} + YY_BREAK +case 35: +YY_RULE_SETUP +#line 626 "xLexer.lxx" +ECHO; + YY_BREAK +#line 1668 "lex.yy.c" + case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed xyyin at a new source and called + * xyylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = xyyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( xyywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * xyytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of xyylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), (size_t) num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + xyyrestart(xyyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yy_size_t) ((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + yy_size_t new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) xyyrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + (yy_state_ptr) = (yy_state_buf); + *(yy_state_ptr)++ = yy_current_state; + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 141 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + *(yy_state_ptr)++ = yy_current_state; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + + register YY_CHAR yy_c = 1; + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 141 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 140); + if ( ! yy_is_jam ) + *(yy_state_ptr)++ = yy_current_state; + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up xyytext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + xyyrestart(xyyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( xyywrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve xyytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void xyyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + xyyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + xyy_create_buffer(xyyin,YY_BUF_SIZE ); + } + + xyy_init_buffer(YY_CURRENT_BUFFER,input_file ); + xyy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void xyy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * xyypop_buffer_state(); + * xyypush_buffer_state(new_buffer); + */ + xyyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + xyy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (xyywrap()) processing, but the only time this flag + * is looked at is after xyywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void xyy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + xyyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE xyy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) xyyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in xyy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) xyyalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in xyy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + xyy_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with xyy_create_buffer() + * + */ + void xyy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + xyyfree((void *) b->yy_ch_buf ); + + xyyfree((void *) b ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a xyyrestart() or at EOF. + */ + static void xyy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + xyy_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then xyy_init_buffer was _probably_ + * called from xyyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void xyy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + xyy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void xyypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + xyyensure_buffer_stack(); + + /* This block is copied from xyy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from xyy_switch_to_buffer. */ + xyy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void xyypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + xyy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + xyy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void xyyensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)xyyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in xyyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)xyyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in xyyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE xyy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) xyyalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in xyy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + xyy_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to xyylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * xyy_scan_bytes() instead. + */ +YY_BUFFER_STATE xyy_scan_string (yyconst char * yystr ) +{ + + return xyy_scan_bytes(yystr,strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to xyylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE xyy_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) xyyalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in xyy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = xyy_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in xyy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up xyytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + xyytext[xyyleng] = (yy_hold_char); \ + (yy_c_buf_p) = xyytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + xyyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int xyyget_lineno (void) +{ + + return xyylineno; +} + +/** Get the input stream. + * + */ +FILE *xyyget_in (void) +{ + return xyyin; +} + +/** Get the output stream. + * + */ +FILE *xyyget_out (void) +{ + return xyyout; +} + +/** Get the length of the current token. + * + */ +int xyyget_leng (void) +{ + return xyyleng; +} + +/** Get the current token. + * + */ + +char *xyyget_text (void) +{ + return xyytext; +} + +/** Set the current line number. + * @param line_number + * + */ +void xyyset_lineno (int line_number ) +{ + + xyylineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see xyy_switch_to_buffer + */ +void xyyset_in (FILE * in_str ) +{ + xyyin = in_str ; +} + +void xyyset_out (FILE * out_str ) +{ + xyyout = out_str ; +} + +int xyyget_debug (void) +{ + return xyy_flex_debug; +} + +void xyyset_debug (int bdebug ) +{ + xyy_flex_debug = bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from xyylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = 0; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = (char *) 0; + (yy_init) = 0; + (yy_start) = 0; + + (yy_state_buf) = 0; + (yy_state_ptr) = 0; + (yy_full_match) = 0; + (yy_lp) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + xyyin = stdin; + xyyout = stdout; +#else + xyyin = (FILE *) 0; + xyyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * xyylex_init() + */ + return 0; +} + +/* xyylex_destroy is for both reentrant and non-reentrant scanners. */ +int xyylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + xyy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + xyypop_buffer_state(); + } + + /* Destroy the stack itself. */ + xyyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + xyyfree ( (yy_state_buf) ); + (yy_state_buf) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * xyylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *xyyalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *xyyrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void xyyfree (void * ptr ) +{ + free( (char *) ptr ); /* see xyyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 626 "xLexer.lxx" diff --git a/pandatool/src/xfile/xLexer.lxx b/pandatool/src/xfile/xLexer.lxx new file mode 100644 index 00000000..7a5aac98 --- /dev/null +++ b/pandatool/src/xfile/xLexer.lxx @@ -0,0 +1,624 @@ +/** + * @file xLexer.lxx + * @author drose + * @date 2004-10-03 + */ + +%{ +#include "xLexerDefs.h" +#include "xParserDefs.h" +#include "xParser.h" +#include "indent.h" +#include "string_utils.h" +#include "config_xfile.h" + +static int yyinput(void); // declared by flex. +extern "C" int xyywrap(); + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number and col_number as we parse the file, so +// that we can report the position of an error. +int x_line_number = 0; +int x_col_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +char x_current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static std::istream *input_p = nullptr; + +// This is the name of the x file we're parsing. We keep it so we +// can print it out for error messages. +static std::string x_filename; + + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the lexer. +//////////////////////////////////////////////////////////////////// + +void +x_init_lexer(std::istream &in, const std::string &filename) { + input_p = ∈ + x_filename = filename; + x_line_number = 0; + x_col_number = 0; + error_count = 0; + warning_count = 0; +} + +int +x_error_count() { + return error_count; +} + +int +x_warning_count() { + return warning_count; +} + + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +xyywrap(void) { + return 1; +} + +void +xyyerror(const std::string &msg) { + xyyerror(msg, x_line_number, x_col_number, x_current_line); +} + +void +xyyerror(const std::string &msg, int line_number, int col_number, + const std::string ¤t_line) { + xfile_cat.error(false) << "\nError"; + if (!x_filename.empty()) { + xfile_cat.error(false) << " in " << x_filename; + } + xfile_cat.error(false) + << " at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(xfile_cat.error(false), col_number-1) + << "^\n" << msg << "\n\n"; + + error_count++; +} + +void +xyywarning(const std::string &msg) { + xfile_cat.warning(false) << "\nWarning"; + if (!x_filename.empty()) { + xfile_cat.warning(false) << " in " << x_filename; + } + xfile_cat.warning(false) + << " at line " << x_line_number << ", column " << x_col_number << ":\n" + << x_current_line << "\n"; + indent(xfile_cat.warning(false), x_col_number-1) + << "^\n" << msg << "\n\n"; + + warning_count++; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + nassertv(input_p != nullptr); + if (*input_p) { + input_p->read(buffer, max_size); + result = input_p->gcount(); + if (result >= 0 && result < max_size) { + // Truncate at the end of the read. + buffer[result] = '\0'; + } + + if (x_line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the x_current_line array. This + // is because the \n.* rule below, which fills x_current_line + // normally, doesn't catch the first line. + strncpy(x_current_line, xyytext, max_error_width); + x_current_line[max_error_width] = '\0'; + x_line_number++; + x_col_number = 0; + + // Truncate it at the newline. + char *end = strchr(x_current_line, '\n'); + if (end != nullptr) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT + +// Define this macro carefully, since different flex versions call it +// with a different type for result. +#define YY_INPUT(buffer, result, max_size) { \ + int int_result = 0; \ + input_chars((buffer), int_result, (max_size)); \ + (result) = int_result; \ +} + +// read_char reads and returns a single character, incrementing the +// supplied line and column numbers as appropriate. A convenience +// function for the scanning functions below. +static int +read_char(int &line, int &col) { + int c = yyinput(); + if (c == '\n') { + line++; + col = 0; + } else { + col++; + } + return c; +} + +// scan_quoted_string reads a string delimited by quotation marks and +// returns it. +static std::string +scan_quoted_string(char quote_mark) { + std::string result; + + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global x_line_number and x_col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = x_line_number; + int col = x_col_number; + + int c; + c = read_char(line, col); + while (c != quote_mark && c != EOF) { + // A newline is not allowed within a string unless it is escaped. + if (c == '\n') { + c = EOF; + break; + + } else if (c == '\\') { + // Backslash escapes the following character. We also respect + // some C conventions. + c = read_char(line, col); + switch (c) { + case 'a': + result += '\a'; + c = read_char(line, col); + break; + + case 'n': + result += '\n'; + c = read_char(line, col); + break; + + case 'r': + result += '\r'; + c = read_char(line, col); + break; + + case 't': + result += '\t'; + c = read_char(line, col); + break; + + case 'x': + { + int hex = 0; + c = read_char(line, col); + for (int i = 0; i < 2 && isxdigit(c); i++) { + hex = hex * 16 + (isdigit(c) ? c - '0' : tolower(c) - 'a' + 10); + c = read_char(line, col); + } + + result += hex; + } + break; + + case '0': + { + int oct = 0; + c = read_char(line, col); + for (int i = 0; i < 3 && (c >= '0' && c < '7'); i++) { + oct = oct * 8 + (c - '0'); + c = read_char(line, col); + } + + result += oct; + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int dec = 0; + c = read_char(line, col); + for (int i = 0; i < 3 && isdigit(c); i++) { + dec = dec * 10 + (c - '0'); + c = read_char(line, col); + } + + result += dec; + } + break; + + case EOF: + break; + + default: + result += c; + c = read_char(line, col); + } + + } else { + result += c; + c = read_char(line, col); + } + } + + if (c == EOF) { + xyyerror("This quotation mark is unterminated."); + } + + x_line_number = line; + x_col_number = col; + + return result; +} + +// scan_guid_string reads a string of hexadecimal digits delimited by +// angle brackets and returns the corresponding string. +static std::string +scan_guid_string() { + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global x_line_number and x_col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = x_line_number; + int col = x_col_number; + + int num_digits = 0; + int num_hyphens = 0; + + std::string result; + + int c; + c = read_char(line, col); + while (c != '>' && c != EOF) { + if (isxdigit(c)) { + num_digits++; + + } else if (c == '-') { + num_hyphens++; + + } else { + x_line_number = line; + x_col_number = col; + xyyerror("Invalid character in GUID."); + return std::string(); + } + + result += c; + + c = read_char(line, col); + } + + if (c == EOF) { + xyyerror("This GUID string is unterminated."); + return std::string(); + + } else if (num_digits != 32) { + xyyerror("Incorrect number of hex digits in GUID."); + return std::string(); + + } else if (num_hyphens != 4) { + xyyerror("Incorrect number of hyphens in GUID."); + return std::string(); + } + + x_line_number = line; + x_col_number = col; + + return result; +} + +// Parses the text into a list of integers and returns them. +static PTA_int +scan_int_list(const std::string &text) { + PTA_int result; + + vector_string words; + tokenize(text, words, ",;"); + + vector_string::const_iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + std::string trimmed = trim(*wi); + if (!trimmed.empty()) { + int number = 0; + string_to_int(trimmed, number); + result.push_back(number); + } + } + + return result; +} + +// Parses the text into a list of doubles and returns them. +static PTA_double +scan_double_list(const std::string &text) { + PTA_double result; + + vector_string words; + tokenize(text, words, ",;"); + + vector_string::const_iterator wi; + for (wi = words.begin(); wi != words.end(); ++wi) { + std::string trimmed = trim(*wi); + if (!trimmed.empty()) { + double number = 0.0; + string_to_double(trimmed, number); + result.push_back(number); + } + } + + return result; +} + + + +// accept() is called below as each piece is pulled off and +// accepted by the lexer; it increments the current column number. +inline void accept() { + x_col_number += yyleng; +} + +%} + +INTEGERNUM ([+-]?([0-9]+)) +REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) +SEPARATOR [ \t;,]+ +WHITESPACE [ ]+ + +%% + +%{ +%} + +\n.* { + // New line. Save a copy of the line so we can print it out for the + // benefit of the user in case we get an error. + + strncpy(x_current_line, xyytext+1, max_error_width); + x_current_line[max_error_width] = '\0'; + x_line_number++; + x_col_number=0; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + +[ \t\r] { + // Eat whitespace. + accept(); +} + +"//".* { + // Eat C++-style comments. + accept(); +} + +"#".* { + // Eat sh-style comments. + accept(); +} + +"{" { + accept(); + return TOKEN_OBRACE; +} + +"}" { + accept(); + return TOKEN_CBRACE; +} + +"[" { + accept(); + return TOKEN_OBRACKET; +} + +"]" { + accept(); + return TOKEN_CBRACKET; +} + +"." { + accept(); + return TOKEN_DOT; +} + +"," { + accept(); + return TOKEN_COMMA; +} + +";" { + accept(); + return TOKEN_SEMICOLON; +} + +"array" { + accept(); + return TOKEN_ARRAY; +} + +"byte" { + accept(); + return TOKEN_UCHAR; +} + +"char" { + accept(); + return TOKEN_CHAR; +} + +"cstring" { + accept(); + return TOKEN_CSTRING; +} + +"double" { + accept(); + return TOKEN_DOUBLE; +} + +"dword" { + accept(); + return TOKEN_DWORD; +} + +"sdword" { + accept(); + return TOKEN_SDWORD; +} + +"float" { + accept(); + return TOKEN_FLOAT; +} + +"string" { + accept(); + return TOKEN_LPSTR; +} + +"template" { + accept(); + return TOKEN_TEMPLATE; +} + +"uchar" { + accept(); + return TOKEN_UCHAR; +} + +"unicode" { + accept(); + return TOKEN_UNICODE; +} + +"sword" { + accept(); + return TOKEN_SWORD; +} + +"word" { + accept(); + return TOKEN_WORD; +} + +{INTEGERNUM}({WHITESPACE})? { + // A signed or unsigned integer number. + accept(); + xyylval.u.number = atol(xyytext); + xyylval.str = trim_right(xyytext); + + return TOKEN_INTEGER; +} + +({INTEGERNUM}{SEPARATOR})+({INTEGERNUM})? { + // An integer as part of a semicolon- or comma-delimited list. + accept(); + xyylval.int_list = scan_int_list(xyytext); + + return TOKEN_INTEGER_LIST; +} + +({INTEGERNUM}{SEPARATOR})+/{INTEGERNUM}[.] { + // This rule is used to match an integer list that is followed by a + // floating-point number. It's designed to prevent "0;0.5" from + // being interpreted as "0;0" followed by ".5". + accept(); + xyylval.int_list = scan_int_list(xyytext); + + return TOKEN_INTEGER_LIST; +} + +({REALNUM}{SEPARATOR})+({REALNUM})? { + // A floating-point number as part of a semicolon- or comma-delimited list. + accept(); + xyylval.double_list = scan_double_list(xyytext); + + return TOKEN_REALNUM_LIST; +} + +["] { + // Quoted string. + accept(); + xyylval.str = scan_quoted_string('"'); + return TOKEN_STRING; +} + +[<] { + // Long GUID string. + accept(); + xyylval.str = scan_guid_string(); + + if (!xyylval.guid.parse_string(xyylval.str)) { + xyyerror("Malformed GUID."); + } + + return TOKEN_GUID; +} + +[A-Za-z_()-][A-Za-z0-9_.()-]* { + // Identifier. + accept(); + xyylval.str = xyytext; + return TOKEN_NAME; +} + +[0-9-]+[A-Za-z_()-][A-Za-z0-9_.()-]* { + // Identifier with leading digit. + accept(); + xyylval.str = xyytext; + return TOKEN_NAME; +} + +. { + // Any other character is invalid. + accept(); + xyyerror("Invalid character '" + std::string(xyytext) + "'."); +} diff --git a/pandatool/src/xfile/xLexerDefs.h b/pandatool/src/xfile/xLexerDefs.h new file mode 100644 index 00000000..c256cd0d --- /dev/null +++ b/pandatool/src/xfile/xLexerDefs.h @@ -0,0 +1,34 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xLexerDefs.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XLEXERDEFS_H +#define XLEXERDEFS_H + +#include "pandatoolbase.h" + +void x_init_lexer(std::istream &in, const std::string &filename); +int x_error_count(); +int x_warning_count(); + +void xyyerror(const std::string &msg); +void xyyerror(const std::string &msg, int line_number, int col_number, + const std::string ¤t_line); +void xyywarning(const std::string &msg); + +int xyylex(); + +extern int x_line_number; +extern int x_col_number; +extern char x_current_line[]; + +#endif diff --git a/pandatool/src/xfile/xParser.cxx.prebuilt b/pandatool/src/xfile/xParser.cxx.prebuilt new file mode 100644 index 00000000..03b2d8e6 --- /dev/null +++ b/pandatool/src/xfile/xParser.cxx.prebuilt @@ -0,0 +1,2166 @@ +/* A Bison parser, made by GNU Bison 2.4.2. */ + +/* Skeleton implementation for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software + Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.4.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse xyyparse +#define yylex xyylex +#define yyerror xyyerror +#define yylval xyylval +#define yychar xyychar +#define yydebug xyydebug +#define yynerrs xyynerrs + + +/* Copy the first part of user declarations. */ + +/* Line 189 of yacc.c */ +#line 12 "xParser.yxx" + +#include "xLexerDefs.h" +#include "xParserDefs.h" +#include "xFile.h" +#include "xFileTemplate.h" +#include "xFileDataDef.h" +#include "xFileArrayDef.h" +#include "xFileDataNodeTemplate.h" +#include "xFileDataNodeReference.h" +#include "pointerTo.h" +#include "dcast.h" + +// Because our token type contains objects of type string, which +// require correct copy construction (and not simply memcpying), we +// cannot use bison's built-in auto-stack-grow feature. As an easy +// solution, we ensure here that we have enough yacc stack to start +// with, and that it doesn't ever try to grow. +#define YYINITDEPTH 1000 +#define YYMAXDEPTH 1000 + +static XFile *x_file = nullptr; +static XFileNode *current_node = nullptr; +static PT(XFileDataDef) current_data_def; + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +void +x_init_parser(std::istream &in, const std::string &filename, XFile &file) { + x_file = &file; + current_node = &file; + x_init_lexer(in, filename); +} + +void +x_cleanup_parser() { + x_file = nullptr; + current_node = nullptr; +} + + + +/* Line 189 of yacc.c */ +#line 124 "y.tab.c" + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + TOKEN_NAME = 1, + TOKEN_STRING = 2, + TOKEN_INTEGER = 3, + TOKEN_GUID = 5, + TOKEN_INTEGER_LIST = 6, + TOKEN_REALNUM_LIST = 7, + TOKEN_OBRACE = 10, + TOKEN_CBRACE = 11, + TOKEN_OPAREN = 12, + TOKEN_CPAREN = 13, + TOKEN_OBRACKET = 14, + TOKEN_CBRACKET = 15, + TOKEN_OANGLE = 16, + TOKEN_CANGLE = 17, + TOKEN_DOT = 18, + TOKEN_COMMA = 19, + TOKEN_SEMICOLON = 20, + TOKEN_TEMPLATE = 31, + TOKEN_WORD = 40, + TOKEN_DWORD = 41, + TOKEN_FLOAT = 42, + TOKEN_DOUBLE = 43, + TOKEN_CHAR = 44, + TOKEN_UCHAR = 45, + TOKEN_SWORD = 46, + TOKEN_SDWORD = 47, + TOKEN_VOID = 48, + TOKEN_LPSTR = 49, + TOKEN_UNICODE = 50, + TOKEN_CSTRING = 51, + TOKEN_ARRAY = 52 + }; +#endif +/* Tokens. */ +#define TOKEN_NAME 1 +#define TOKEN_STRING 2 +#define TOKEN_INTEGER 3 +#define TOKEN_GUID 5 +#define TOKEN_INTEGER_LIST 6 +#define TOKEN_REALNUM_LIST 7 +#define TOKEN_OBRACE 10 +#define TOKEN_CBRACE 11 +#define TOKEN_OPAREN 12 +#define TOKEN_CPAREN 13 +#define TOKEN_OBRACKET 14 +#define TOKEN_CBRACKET 15 +#define TOKEN_OANGLE 16 +#define TOKEN_CANGLE 17 +#define TOKEN_DOT 18 +#define TOKEN_COMMA 19 +#define TOKEN_SEMICOLON 20 +#define TOKEN_TEMPLATE 31 +#define TOKEN_WORD 40 +#define TOKEN_DWORD 41 +#define TOKEN_FLOAT 42 +#define TOKEN_DOUBLE 43 +#define TOKEN_CHAR 44 +#define TOKEN_UCHAR 45 +#define TOKEN_SWORD 46 +#define TOKEN_SDWORD 47 +#define TOKEN_VOID 48 +#define TOKEN_LPSTR 49 +#define TOKEN_UNICODE 50 +#define TOKEN_CSTRING 51 +#define TOKEN_ARRAY 52 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + + +/* Copy the second part of user declarations. */ + + +/* Line 264 of yacc.c */ +#line 231 "y.tab.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int yyi) +#else +static int +YYID (yyi) + int yyi; +#endif +{ + return yyi; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined _STDLIB_H \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 3 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 114 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 34 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 35 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 71 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 100 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 257 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 3, 4, 5, 2, 6, 7, 8, 2, 2, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 20, 2, 2, 2, 2, 2, 2, 2, 2, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint8 yyprhs[] = +{ + 0, 0, 3, 5, 8, 11, 14, 15, 23, 28, + 30, 32, 34, 36, 38, 40, 43, 45, 47, 49, + 53, 58, 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 87, 90, 92, 95, 99, 101, + 103, 105, 108, 110, 113, 115, 117, 120, 123, 125, + 127, 129, 131, 133, 137, 138, 146, 148, 151, 155, + 157, 159, 161, 164, 166, 168, 170, 172, 174, 176, + 178, 181 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 35, 0, -1, 68, -1, 35, 36, -1, 35, 59, + -1, 35, 10, -1, -1, 20, 53, 9, 56, 37, + 38, 10, -1, 39, 13, 40, 14, -1, 41, -1, + 68, -1, 41, -1, 58, -1, 51, -1, 42, -1, + 41, 42, -1, 43, -1, 44, -1, 45, -1, 46, + 55, 19, -1, 33, 47, 48, 19, -1, 53, 55, + 19, -1, 21, -1, 22, -1, 23, -1, 24, -1, + 25, -1, 26, -1, 27, -1, 28, -1, 30, -1, + 31, -1, 32, -1, 46, 54, -1, 53, 54, -1, + 49, -1, 48, 49, -1, 13, 50, 14, -1, 5, + -1, 54, -1, 52, -1, 51, 52, -1, 53, -1, + 53, 56, -1, 3, -1, 3, -1, 54, 3, -1, + 54, 5, -1, 68, -1, 54, -1, 6, -1, 68, + -1, 56, -1, 17, 17, 17, -1, -1, 53, 55, + 9, 60, 57, 61, 10, -1, 68, -1, 61, 62, + -1, 9, 67, 10, -1, 59, -1, 63, -1, 64, + -1, 65, 66, -1, 66, -1, 7, -1, 8, -1, + 4, -1, 19, -1, 18, -1, 54, -1, 54, 56, + -1, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 108, 108, 109, 110, 111, 116, 115, 130, 131, + 135, 136, 140, 144, 148, 149, 153, 154, 155, 159, + 167, 171, 184, 188, 192, 196, 200, 204, 208, 212, + 216, 220, 224, 231, 236, 249, 250, 254, 258, 262, + 274, 277, 283, 292, 308, 312, 313, 317, 324, 328, + 332, 336, 340, 344, 349, 348, 376, 377, 381, 386, + 390, 398, 406, 414, 420, 424, 428, 432, 433, 437, + 446, 462 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "TOKEN_NAME", "TOKEN_STRING", + "TOKEN_INTEGER", "TOKEN_GUID", "TOKEN_INTEGER_LIST", + "TOKEN_REALNUM_LIST", "TOKEN_OBRACE", "TOKEN_CBRACE", "TOKEN_OPAREN", + "TOKEN_CPAREN", "TOKEN_OBRACKET", "TOKEN_CBRACKET", "TOKEN_OANGLE", + "TOKEN_CANGLE", "TOKEN_DOT", "TOKEN_COMMA", "TOKEN_SEMICOLON", + "TOKEN_TEMPLATE", "TOKEN_WORD", "TOKEN_DWORD", "TOKEN_FLOAT", + "TOKEN_DOUBLE", "TOKEN_CHAR", "TOKEN_UCHAR", "TOKEN_SWORD", + "TOKEN_SDWORD", "TOKEN_VOID", "TOKEN_LPSTR", "TOKEN_UNICODE", + "TOKEN_CSTRING", "TOKEN_ARRAY", "$accept", "xfile", "template", "@1", + "template_parts", "template_members_part", "template_option_info", + "template_members_list", "template_members", "primitive", "array", + "template_reference", "primitive_type", "array_data_type", + "dimension_list", "dimension", "dimension_size", "template_option_list", + "template_option_part", "singleword_name", "multiword_name", + "optional_multiword_name", "class_id", "optional_class_id", "ellipsis", + "object", "@2", "data_parts_list", "data_part", "integer_list", + "realnum_list", "string", "list_separator", "data_reference", "empty", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 1, 2, 3, 5, 6, 7, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 31, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 34, 35, 35, 35, 35, 37, 36, 38, 38, + 39, 39, 40, 40, 41, 41, 42, 42, 42, 43, + 44, 45, 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 47, 47, 48, 48, 49, 50, 50, + 51, 51, 52, 52, 53, 54, 54, 54, 55, 55, + 56, 57, 57, 58, 60, 59, 61, 61, 62, 62, + 62, 62, 62, 62, 63, 64, 65, 66, 66, 67, + 67, 68 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 2, 2, 2, 0, 7, 4, 1, + 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, + 4, 3, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 1, 2, 3, 1, 1, + 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, + 1, 1, 1, 3, 0, 7, 1, 2, 3, 1, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 2, 0 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 71, 0, 2, 1, 44, 5, 0, 3, 71, 4, + 0, 45, 49, 0, 48, 0, 46, 47, 54, 50, + 6, 71, 71, 52, 71, 51, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 0, 0, 0, + 9, 14, 16, 17, 18, 71, 71, 10, 0, 56, + 0, 0, 0, 7, 0, 15, 0, 0, 66, 64, + 65, 0, 55, 68, 67, 59, 57, 60, 61, 0, + 63, 33, 0, 0, 35, 34, 0, 0, 13, 40, + 42, 12, 19, 21, 69, 0, 62, 38, 0, 39, + 20, 36, 0, 8, 41, 43, 70, 58, 37, 53 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + -1, 1, 7, 22, 38, 39, 77, 40, 41, 42, + 43, 44, 45, 51, 73, 74, 88, 78, 79, 8, + 12, 13, 20, 24, 81, 9, 21, 48, 66, 67, + 68, 69, 70, 85, 14 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -48 +static const yytype_int8 yypact[] = +{ + -48, 8, -48, -48, -48, -48, 9, -48, 40, -48, + 41, -48, 4, 42, -48, 43, -48, -48, -48, -48, + -48, 43, 52, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48, 65, 44, 39, + 14, -48, -48, -48, -48, 40, 40, -48, 95, -48, + 40, 45, 40, -48, 3, -48, 34, 37, -48, -48, + -48, 40, -48, -48, -48, -48, -48, -48, -48, 11, + -48, 4, 10, -9, -48, 4, 46, 47, 9, -48, + 43, -48, -48, -48, 16, 49, -48, -48, 48, 4, + -48, -48, 50, -48, -48, -48, -48, -48, -48, -48 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -48, -48, -48, -48, -48, -48, -48, -48, 17, -48, + -48, -48, 28, -48, -48, -7, -48, -48, -8, -6, + -47, -13, -20, -48, -48, 21, -48, -48, -48, -48, + -48, -48, 12, -48, 2 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -12 +static const yytype_int8 yytable[] = +{ + 10, 23, 2, 71, 72, 75, 4, 16, 3, 17, + 90, 4, 4, 11, 84, 87, 46, 4, 5, 16, + 76, 17, 19, 25, 47, 89, 49, -11, 6, 63, + 64, 52, 56, 57, 46, 26, 27, 28, 29, 30, + 31, 32, 33, 11, 34, 35, 36, 37, 80, 19, + 15, 18, 54, 82, 53, 4, 83, 55, 72, 97, + 95, 93, 98, 92, 96, 50, 91, 99, 4, 65, + 94, 0, 80, 26, 27, 28, 29, 30, 31, 32, + 33, 86, 34, 35, 36, 37, 26, 27, 28, 29, + 30, 31, 32, 33, 0, 34, 35, 36, 4, 58, + 0, 0, 59, 60, 61, 62, 0, 0, 0, 0, + 0, 0, 0, 63, 64 +}; + +static const yytype_int8 yycheck[] = +{ + 6, 21, 0, 50, 13, 52, 3, 3, 0, 5, + 19, 3, 3, 3, 61, 5, 22, 3, 10, 3, + 17, 5, 6, 21, 22, 72, 24, 13, 20, 18, + 19, 37, 45, 46, 40, 21, 22, 23, 24, 25, + 26, 27, 28, 3, 30, 31, 32, 33, 54, 6, + 9, 9, 13, 19, 10, 3, 19, 40, 13, 10, + 80, 14, 14, 17, 84, 37, 73, 17, 3, 48, + 78, -1, 78, 21, 22, 23, 24, 25, 26, 27, + 28, 69, 30, 31, 32, 33, 21, 22, 23, 24, + 25, 26, 27, 28, -1, 30, 31, 32, 3, 4, + -1, -1, 7, 8, 9, 10, -1, -1, -1, -1, + -1, -1, -1, 18, 19 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 35, 68, 0, 3, 10, 20, 36, 53, 59, + 53, 3, 54, 55, 68, 9, 3, 5, 9, 6, + 56, 60, 37, 56, 57, 68, 21, 22, 23, 24, + 25, 26, 27, 28, 30, 31, 32, 33, 38, 39, + 41, 42, 43, 44, 45, 46, 53, 68, 61, 68, + 46, 47, 53, 10, 13, 42, 55, 55, 4, 7, + 8, 9, 10, 18, 19, 59, 62, 63, 64, 65, + 66, 54, 13, 48, 49, 54, 17, 40, 51, 52, + 53, 58, 19, 19, 54, 67, 66, 5, 50, 54, + 19, 49, 17, 14, 52, 56, 56, 10, 14, 17 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. However, + YYFAIL appears to be in use. Nevertheless, it is formally deprecated + in Bison 2.4.2's NEWS entry, where a plan to phase it out is + discussed. */ + +#define YYFAIL goto yyerrlab +#if defined YYFAIL + /* This is here to suppress warnings from the GCC cpp's + -Wunused-macros. Normally we don't worry about that warning, but + some users do, and we want to make it easy for users to remove + YYFAIL uses, which will produce warnings from Bison 2.5. */ +#endif + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE const * const yyvaluep; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + yy_symbol_value_print (yyoutput, yytype, yyvaluep); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +#else +static void +yy_stack_print (yybottom, yytop) + yytype_int16 *yybottom; + yytype_int16 *yytop; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, int yyrule) +#else +static void +yy_reduce_print (yyvsp, yyrule) + YYSTYPE *yyvsp; + int yyrule; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, Rule); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + YYUSE (yyvaluep); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + + default: + break; + } +} + +/* Prevent warnings from -Wmissing-prototypes. */ +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*-------------------------. +| yyparse or yypush_parse. | +`-------------------------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + `yyss': related to states. + `yyvs': related to semantic values. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yytoken = 0; + yyss = yyssa; + yyvs = yyvsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + yyssp = yyss; + yyvsp = yyvs; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 6: + +/* Line 1464 of yacc.c */ +#line 116 "xParser.yxx" + { + (yyval.u.node) = current_node; + XFileTemplate *templ = new XFileTemplate(x_file, (yyvsp[(2) - (4)].str), (yyvsp[(4) - (4)].guid)); + current_node->add_child(templ); + current_node = templ; +} + break; + + case 7: + +/* Line 1464 of yacc.c */ +#line 123 "xParser.yxx" + { + (yyval.u.node) = current_node; + current_node = (yyvsp[(5) - (7)].u.node); +} + break; + + case 12: + +/* Line 1464 of yacc.c */ +#line 141 "xParser.yxx" + { + DCAST(XFileTemplate, current_node)->set_open(true); +} + break; + + case 19: + +/* Line 1464 of yacc.c */ +#line 160 "xParser.yxx" + { + current_data_def = new XFileDataDef(x_file, (yyvsp[(2) - (3)].str), (yyvsp[(1) - (3)].u.primitive_type)); + current_node->add_child(current_data_def); +} + break; + + case 21: + +/* Line 1464 of yacc.c */ +#line 172 "xParser.yxx" + { + XFileTemplate *xtemplate = x_file->find_template((yyvsp[(1) - (3)].str)); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + (yyvsp[(1) - (3)].str)); + } else { + current_data_def = new XFileDataDef(x_file, (yyvsp[(2) - (3)].str), XFileDataDef::T_template, xtemplate); + current_node->add_child(current_data_def); + } +} + break; + + case 22: + +/* Line 1464 of yacc.c */ +#line 185 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_word; +} + break; + + case 23: + +/* Line 1464 of yacc.c */ +#line 189 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_dword; +} + break; + + case 24: + +/* Line 1464 of yacc.c */ +#line 193 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_float; +} + break; + + case 25: + +/* Line 1464 of yacc.c */ +#line 197 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_double; +} + break; + + case 26: + +/* Line 1464 of yacc.c */ +#line 201 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_char; +} + break; + + case 27: + +/* Line 1464 of yacc.c */ +#line 205 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_uchar; +} + break; + + case 28: + +/* Line 1464 of yacc.c */ +#line 209 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_sword; +} + break; + + case 29: + +/* Line 1464 of yacc.c */ +#line 213 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_sdword; +} + break; + + case 30: + +/* Line 1464 of yacc.c */ +#line 217 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_string; +} + break; + + case 31: + +/* Line 1464 of yacc.c */ +#line 221 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_unicode; +} + break; + + case 32: + +/* Line 1464 of yacc.c */ +#line 225 "xParser.yxx" + { + (yyval.u.primitive_type) = XFileDataDef::T_cstring; +} + break; + + case 33: + +/* Line 1464 of yacc.c */ +#line 232 "xParser.yxx" + { + current_data_def = new XFileDataDef(x_file, (yyvsp[(2) - (2)].str), (yyvsp[(1) - (2)].u.primitive_type)); + current_node->add_child(current_data_def); +} + break; + + case 34: + +/* Line 1464 of yacc.c */ +#line 237 "xParser.yxx" + { + XFileTemplate *xtemplate = x_file->find_template((yyvsp[(1) - (2)].str)); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + (yyvsp[(1) - (2)].str)); + } else { + current_data_def = new XFileDataDef(x_file, (yyvsp[(2) - (2)].str), XFileDataDef::T_template, xtemplate); + current_node->add_child(current_data_def); + } +} + break; + + case 38: + +/* Line 1464 of yacc.c */ +#line 259 "xParser.yxx" + { + current_data_def->add_array_def(XFileArrayDef((yyvsp[(1) - (1)].u.number))); +} + break; + + case 39: + +/* Line 1464 of yacc.c */ +#line 263 "xParser.yxx" + { + XFileNode *data_def = current_node->find_child((yyvsp[(1) - (1)].str)); + if (data_def == nullptr) { + yyerror("Unknown identifier: " + (yyvsp[(1) - (1)].str)); + } else { + current_data_def->add_array_def(XFileArrayDef(DCAST(XFileDataDef, data_def))); + } +} + break; + + case 40: + +/* Line 1464 of yacc.c */ +#line 275 "xParser.yxx" + { +} + break; + + case 41: + +/* Line 1464 of yacc.c */ +#line 278 "xParser.yxx" + { +} + break; + + case 42: + +/* Line 1464 of yacc.c */ +#line 284 "xParser.yxx" + { + XFileTemplate *xtemplate = x_file->find_template((yyvsp[(1) - (1)].str)); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + (yyvsp[(1) - (1)].str)); + } else { + DCAST(XFileTemplate, current_node)->add_option(xtemplate); + } +} + break; + + case 43: + +/* Line 1464 of yacc.c */ +#line 293 "xParser.yxx" + { + XFileTemplate *xtemplate = x_file->find_template((yyvsp[(2) - (2)].guid)); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + (yyvsp[(1) - (2)].str)); + } else { + if (xtemplate->get_name() != (yyvsp[(1) - (2)].str)) { + xyywarning("GUID identifies template " + xtemplate->get_name() + + ", not " + (yyvsp[(1) - (2)].str)); + } + DCAST(XFileTemplate, current_node)->add_option(xtemplate); + } +} + break; + + case 46: + +/* Line 1464 of yacc.c */ +#line 314 "xParser.yxx" + { + (yyval.str) = (yyvsp[(1) - (2)].str) + " " + (yyvsp[(2) - (2)].str); +} + break; + + case 47: + +/* Line 1464 of yacc.c */ +#line 318 "xParser.yxx" + { + (yyval.str) = (yyvsp[(1) - (2)].str) + " " + (yyvsp[(2) - (2)].str); +} + break; + + case 48: + +/* Line 1464 of yacc.c */ +#line 325 "xParser.yxx" + { + (yyval.str) = std::string(); +} + break; + + case 51: + +/* Line 1464 of yacc.c */ +#line 337 "xParser.yxx" + { + (yyval.guid) = WindowsGuid(); +} + break; + + case 54: + +/* Line 1464 of yacc.c */ +#line 349 "xParser.yxx" + { + XFileTemplate *xtemplate = x_file->find_template((yyvsp[(1) - (3)].str)); + (yyval.u.node) = current_node; + + if (xtemplate == nullptr) { + yyerror("Unknown template: " + (yyvsp[(1) - (3)].str)); + } else { + XFileDataNodeTemplate *templ = + new XFileDataNodeTemplate(x_file, (yyvsp[(2) - (3)].str), xtemplate); + current_node->add_child(templ); + current_node = templ; + } +} + break; + + case 55: + +/* Line 1464 of yacc.c */ +#line 363 "xParser.yxx" + { + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->finalize_parse_data(); + } + + (yyval.u.node) = current_node; + current_node = (yyvsp[(4) - (7)].u.node); +} + break; + + case 58: + +/* Line 1464 of yacc.c */ +#line 382 "xParser.yxx" + { + // nested references should be added as children too. + current_node->add_child((yyvsp[(2) - (3)].u.node)); +} + break; + + case 59: + +/* Line 1464 of yacc.c */ +#line 387 "xParser.yxx" + { + // nested objects are just quietly added as children. +} + break; + + case 60: + +/* Line 1464 of yacc.c */ +#line 391 "xParser.yxx" + { + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_int((yyvsp[(1) - (1)].int_list)); + } +} + break; + + case 61: + +/* Line 1464 of yacc.c */ +#line 399 "xParser.yxx" + { + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_double((yyvsp[(1) - (1)].double_list)); + } +} + break; + + case 62: + +/* Line 1464 of yacc.c */ +#line 407 "xParser.yxx" + { + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_string((yyvsp[(1) - (2)].str)); + } +} + break; + + case 63: + +/* Line 1464 of yacc.c */ +#line 415 "xParser.yxx" + { +} + break; + + case 69: + +/* Line 1464 of yacc.c */ +#line 438 "xParser.yxx" + { + XFileDataNodeTemplate *data_object = x_file->find_data_object((yyvsp[(1) - (1)].str)); + if (data_object == nullptr) { + yyerror("Unknown data_object: " + (yyvsp[(1) - (1)].str)); + } + + (yyval.u.node) = new XFileDataNodeReference(data_object); +} + break; + + case 70: + +/* Line 1464 of yacc.c */ +#line 447 "xParser.yxx" + { + XFileDataNodeTemplate *data_object = x_file->find_data_object((yyvsp[(2) - (2)].guid)); + if (data_object == nullptr) { + yyerror("Unknown data_object: " + (yyvsp[(1) - (2)].str)); + } else { + if (data_object->get_name() != (yyvsp[(1) - (2)].str)) { + xyywarning("GUID identifies data_object " + data_object->get_name() + + ", not " + (yyvsp[(1) - (2)].str)); + } + } + + (yyval.u.node) = new XFileDataNodeReference(data_object); +} + break; + + + +/* Line 1464 of yacc.c */ +#line 1957 "y.tab.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (yymsg); + } + else + { + yyerror (YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + yystos[yystate], yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined(yyoverflow) || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + /* Make sure YYID is used. */ + return YYID (yyresult); +} + + + diff --git a/pandatool/src/xfile/xParser.h.prebuilt b/pandatool/src/xfile/xParser.h.prebuilt new file mode 100644 index 00000000..ab80d042 --- /dev/null +++ b/pandatool/src/xfile/xParser.h.prebuilt @@ -0,0 +1,118 @@ +/* A Bison parser, made by GNU Bison 2.4.2. */ + +/* Skeleton interface for Bison's Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software + Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + TOKEN_NAME = 1, + TOKEN_STRING = 2, + TOKEN_INTEGER = 3, + TOKEN_GUID = 5, + TOKEN_INTEGER_LIST = 6, + TOKEN_REALNUM_LIST = 7, + TOKEN_OBRACE = 10, + TOKEN_CBRACE = 11, + TOKEN_OPAREN = 12, + TOKEN_CPAREN = 13, + TOKEN_OBRACKET = 14, + TOKEN_CBRACKET = 15, + TOKEN_OANGLE = 16, + TOKEN_CANGLE = 17, + TOKEN_DOT = 18, + TOKEN_COMMA = 19, + TOKEN_SEMICOLON = 20, + TOKEN_TEMPLATE = 31, + TOKEN_WORD = 40, + TOKEN_DWORD = 41, + TOKEN_FLOAT = 42, + TOKEN_DOUBLE = 43, + TOKEN_CHAR = 44, + TOKEN_UCHAR = 45, + TOKEN_SWORD = 46, + TOKEN_SDWORD = 47, + TOKEN_VOID = 48, + TOKEN_LPSTR = 49, + TOKEN_UNICODE = 50, + TOKEN_CSTRING = 51, + TOKEN_ARRAY = 52 + }; +#endif +/* Tokens. */ +#define TOKEN_NAME 1 +#define TOKEN_STRING 2 +#define TOKEN_INTEGER 3 +#define TOKEN_GUID 5 +#define TOKEN_INTEGER_LIST 6 +#define TOKEN_REALNUM_LIST 7 +#define TOKEN_OBRACE 10 +#define TOKEN_CBRACE 11 +#define TOKEN_OPAREN 12 +#define TOKEN_CPAREN 13 +#define TOKEN_OBRACKET 14 +#define TOKEN_CBRACKET 15 +#define TOKEN_OANGLE 16 +#define TOKEN_CANGLE 17 +#define TOKEN_DOT 18 +#define TOKEN_COMMA 19 +#define TOKEN_SEMICOLON 20 +#define TOKEN_TEMPLATE 31 +#define TOKEN_WORD 40 +#define TOKEN_DWORD 41 +#define TOKEN_FLOAT 42 +#define TOKEN_DOUBLE 43 +#define TOKEN_CHAR 44 +#define TOKEN_UCHAR 45 +#define TOKEN_SWORD 46 +#define TOKEN_SDWORD 47 +#define TOKEN_VOID 48 +#define TOKEN_LPSTR 49 +#define TOKEN_UNICODE 50 +#define TOKEN_CSTRING 51 +#define TOKEN_ARRAY 52 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +#endif + +extern YYSTYPE xyylval; + + diff --git a/pandatool/src/xfile/xParser.yxx b/pandatool/src/xfile/xParser.yxx new file mode 100644 index 00000000..0352a4d4 --- /dev/null +++ b/pandatool/src/xfile/xParser.yxx @@ -0,0 +1,464 @@ +/** + * @file xParser.yxx + * @author drose + * @date 2004-10-03 + */ + +// The grammar defined in this file is taken more-or-less from the +// Microsoft DirectX File Format Specification document, version 1.13. +// The document actually describes a slightly incomplete and incorrect +// grammar, so small changes had to be made, but an attempt was made +// to be as faithful as possible to the intention of the spec. + +%{ +#include "xLexerDefs.h" +#include "xParserDefs.h" +#include "xFile.h" +#include "xFileTemplate.h" +#include "xFileDataDef.h" +#include "xFileArrayDef.h" +#include "xFileDataNodeTemplate.h" +#include "xFileDataNodeReference.h" +#include "pointerTo.h" +#include "dcast.h" + +// Because our token type contains objects of type string, which +// require correct copy construction (and not simply memcpying), we +// cannot use bison's built-in auto-stack-grow feature. As an easy +// solution, we ensure here that we have enough yacc stack to start +// with, and that it doesn't ever try to grow. +#define YYINITDEPTH 1000 +#define YYMAXDEPTH 1000 + +static XFile *x_file = nullptr; +static XFileNode *current_node = nullptr; +static PT(XFileDataDef) current_data_def; + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +void +x_init_parser(std::istream &in, const std::string &filename, XFile &file) { + x_file = &file; + current_node = &file; + x_init_lexer(in, filename); +} + +void +x_cleanup_parser() { + x_file = nullptr; + current_node = nullptr; +} + +%} + +// These token values are taken from the DirectX spec; the particular +// numeric values are useful when reading an .x file in binary mode +// (which basically just streams the tokens retrieved by the lexer). + +%token TOKEN_NAME 1 +%token TOKEN_STRING 2 +%token TOKEN_INTEGER 3 +%token TOKEN_GUID 5 +%token TOKEN_INTEGER_LIST 6 +%token TOKEN_REALNUM_LIST 7 + +%token TOKEN_OBRACE 10 +%token TOKEN_CBRACE 11 +%token TOKEN_OPAREN 12 +%token TOKEN_CPAREN 13 +%token TOKEN_OBRACKET 14 +%token TOKEN_CBRACKET 15 +%token TOKEN_OANGLE 16 +%token TOKEN_CANGLE 17 +%token TOKEN_DOT 18 +%token TOKEN_COMMA 19 +%token TOKEN_SEMICOLON 20 +%token TOKEN_TEMPLATE 31 +%token TOKEN_WORD 40 +%token TOKEN_DWORD 41 +%token TOKEN_FLOAT 42 +%token TOKEN_DOUBLE 43 +%token TOKEN_CHAR 44 +%token TOKEN_UCHAR 45 +%token TOKEN_SWORD 46 +%token TOKEN_SDWORD 47 +%token TOKEN_VOID 48 +%token TOKEN_LPSTR 49 +%token TOKEN_UNICODE 50 +%token TOKEN_CSTRING 51 +%token TOKEN_ARRAY 52 + +%type template +%type object +%type primitive_type +%type integer_list +%type realnum_list +%type singleword_name +%type multiword_name +%type optional_multiword_name +%type string +%type class_id +%type optional_class_id +%type data_reference + +%% + +xfile: + empty + | xfile template + | xfile object + | xfile TOKEN_CBRACE /* the 3dsMax converter writes an extra one. */ + ; + +template: + TOKEN_TEMPLATE singleword_name TOKEN_OBRACE class_id +{ + $$ = current_node; + XFileTemplate *templ = new XFileTemplate(x_file, $2, $4); + current_node->add_child(templ); + current_node = templ; +} + template_parts TOKEN_CBRACE +{ + $$ = current_node; + current_node = $5; +} + ; + +template_parts: + template_members_part TOKEN_OBRACKET template_option_info TOKEN_CBRACKET + | template_members_list + ; + +template_members_part: + empty + | template_members_list + ; + +template_option_info: + ellipsis +{ + DCAST(XFileTemplate, current_node)->set_open(true); +} + | template_option_list + ; + +template_members_list: + template_members + | template_members_list template_members + ; + +template_members: + primitive + | array + | template_reference + ; + +primitive: + primitive_type optional_multiword_name TOKEN_SEMICOLON +{ + current_data_def = new XFileDataDef(x_file, $2, $1); + current_node->add_child(current_data_def); +} + ; + +array: + TOKEN_ARRAY array_data_type dimension_list TOKEN_SEMICOLON + ; + +template_reference: + singleword_name optional_multiword_name TOKEN_SEMICOLON +{ + XFileTemplate *xtemplate = x_file->find_template($1); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + $1); + } else { + current_data_def = new XFileDataDef(x_file, $2, XFileDataDef::T_template, xtemplate); + current_node->add_child(current_data_def); + } +} + ; + +primitive_type: + TOKEN_WORD +{ + $$ = XFileDataDef::T_word; +} + | TOKEN_DWORD +{ + $$ = XFileDataDef::T_dword; +} + | TOKEN_FLOAT +{ + $$ = XFileDataDef::T_float; +} + | TOKEN_DOUBLE +{ + $$ = XFileDataDef::T_double; +} + | TOKEN_CHAR +{ + $$ = XFileDataDef::T_char; +} + | TOKEN_UCHAR +{ + $$ = XFileDataDef::T_uchar; +} + | TOKEN_SWORD +{ + $$ = XFileDataDef::T_sword; +} + | TOKEN_SDWORD +{ + $$ = XFileDataDef::T_sdword; +} + | TOKEN_LPSTR +{ + $$ = XFileDataDef::T_string; +} + | TOKEN_UNICODE +{ + $$ = XFileDataDef::T_unicode; +} + | TOKEN_CSTRING +{ + $$ = XFileDataDef::T_cstring; +} + ; + +array_data_type: + primitive_type multiword_name +{ + current_data_def = new XFileDataDef(x_file, $2, $1); + current_node->add_child(current_data_def); +} + | singleword_name multiword_name +{ + XFileTemplate *xtemplate = x_file->find_template($1); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + $1); + } else { + current_data_def = new XFileDataDef(x_file, $2, XFileDataDef::T_template, xtemplate); + current_node->add_child(current_data_def); + } +} + ; + +dimension_list: + dimension + | dimension_list dimension + ; + +dimension: + TOKEN_OBRACKET dimension_size TOKEN_CBRACKET + ; + +dimension_size: + TOKEN_INTEGER +{ + current_data_def->add_array_def(XFileArrayDef($1)); +} + | multiword_name +{ + XFileNode *data_def = current_node->find_child($1); + if (data_def == nullptr) { + yyerror("Unknown identifier: " + $1); + } else { + current_data_def->add_array_def(XFileArrayDef(DCAST(XFileDataDef, data_def))); + } +} + ; + +template_option_list: + template_option_part +{ +} + | template_option_list template_option_part +{ +} + ; + +template_option_part: + singleword_name +{ + XFileTemplate *xtemplate = x_file->find_template($1); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + $1); + } else { + DCAST(XFileTemplate, current_node)->add_option(xtemplate); + } +} + | singleword_name class_id +{ + XFileTemplate *xtemplate = x_file->find_template($2); + if (xtemplate == nullptr) { + yyerror("Unknown template: " + $1); + } else { + if (xtemplate->get_name() != $1) { + xyywarning("GUID identifies template " + xtemplate->get_name() + + ", not " + $1); + } + DCAST(XFileTemplate, current_node)->add_option(xtemplate); + } +} + ; + +singleword_name: + TOKEN_NAME + ; + +multiword_name: + TOKEN_NAME + | multiword_name TOKEN_NAME +{ + $$ = $1 + " " + $2; +} + | multiword_name TOKEN_INTEGER +{ + $$ = $1 + " " + $2; +} + ; + +optional_multiword_name: + empty +{ + $$ = std::string(); +} + | multiword_name + ; + +class_id: + TOKEN_GUID + ; + +optional_class_id: + empty +{ + $$ = WindowsGuid(); +} + | class_id + ; + +ellipsis: + TOKEN_DOT TOKEN_DOT TOKEN_DOT + ; + +object: + singleword_name optional_multiword_name TOKEN_OBRACE +{ + XFileTemplate *xtemplate = x_file->find_template($1); + $$ = current_node; + + if (xtemplate == nullptr) { + yyerror("Unknown template: " + $1); + } else { + XFileDataNodeTemplate *templ = + new XFileDataNodeTemplate(x_file, $2, xtemplate); + current_node->add_child(templ); + current_node = templ; + } +} + optional_class_id data_parts_list TOKEN_CBRACE +{ + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->finalize_parse_data(); + } + + $$ = current_node; + current_node = $4; +} + ; + +data_parts_list: + empty + | data_parts_list data_part + ; + +data_part: + TOKEN_OBRACE data_reference TOKEN_CBRACE +{ + // nested references should be added as children too. + current_node->add_child($2); +} + | object +{ + // nested objects are just quietly added as children. +} + | integer_list +{ + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_int($1); + } +} + | realnum_list +{ + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_double($1); + } +} + | string list_separator +{ + if (current_node->is_exact_type(XFileDataNodeTemplate::get_class_type())) { + XFileDataNodeTemplate *current_template = + DCAST(XFileDataNodeTemplate, current_node); + current_template->add_parse_string($1); + } +} + | list_separator +{ +} + ; + +integer_list: + TOKEN_INTEGER_LIST + ; + +realnum_list: + TOKEN_REALNUM_LIST + ; + +string: + TOKEN_STRING + ; + +list_separator: + TOKEN_SEMICOLON + | TOKEN_COMMA + ; + +data_reference: + multiword_name +{ + XFileDataNodeTemplate *data_object = x_file->find_data_object($1); + if (data_object == nullptr) { + yyerror("Unknown data_object: " + $1); + } + + $$ = new XFileDataNodeReference(data_object); +} + | multiword_name class_id +{ + XFileDataNodeTemplate *data_object = x_file->find_data_object($2); + if (data_object == nullptr) { + yyerror("Unknown data_object: " + $1); + } else { + if (data_object->get_name() != $1) { + xyywarning("GUID identifies data_object " + data_object->get_name() + + ", not " + $1); + } + } + + $$ = new XFileDataNodeReference(data_object); +} + ; + +empty: + ; diff --git a/pandatool/src/xfile/xParserDefs.h b/pandatool/src/xfile/xParserDefs.h new file mode 100644 index 00000000..0900ae42 --- /dev/null +++ b/pandatool/src/xfile/xParserDefs.h @@ -0,0 +1,53 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xParserDefs.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XPARSERDEFS_H +#define XPARSERDEFS_H + +#include "pandatoolbase.h" +#include "windowsGuid.h" +#include "xFileDataDef.h" +#include "pta_int.h" +#include "pta_double.h" + +class XFile; +class XFileNode; + +void x_init_parser(std::istream &in, const std::string &filename, XFile &file); +void x_cleanup_parser(); +int xyyparse(); + +// This structure holds the return value for each token. Traditionally, this +// is a union, and is declared with the %union declaration in the parser.y +// file, but unions are pretty worthless in C++ (you can't include an object +// that has member functions in a union), so we'll use a class instead. That +// means we need to declare it externally, here. + +class XTokenType { +public: + union U { + int number; + XFileNode *node; + XFileDataDef::Type primitive_type; + } u; + std::string str; + WindowsGuid guid; + PTA_double double_list; + PTA_int int_list; +}; + +// The yacc-generated code expects to use the symbol 'YYSTYPE' to refer to the +// above class. +#define YYSTYPE XTokenType + +#endif diff --git a/pandatool/src/xfileegg/CMakeLists.txt b/pandatool/src/xfileegg/CMakeLists.txt new file mode 100644 index 00000000..eb8bd12e --- /dev/null +++ b/pandatool/src/xfileegg/CMakeLists.txt @@ -0,0 +1,26 @@ +if(NOT HAVE_EGG) + return() +endif() + +set(P3XFILEEGG_HEADERS + xFileAnimationSet.h xFileAnimationSet.I + xFileFace.h xFileMaker.h + xFileMaterial.h xFileMesh.h xFileNormal.h + xFileToEggConverter.h + xFileVertex.h xFileVertexPool.h +) + +set(P3XFILEEGG_SOURCES + xFileAnimationSet.cxx + xFileFace.cxx xFileMaker.cxx + xFileMaterial.cxx xFileMesh.cxx xFileNormal.cxx + xFileToEggConverter.cxx + xFileVertex.cxx +) + +composite_sources(p3xfileegg P3XFILEEGG_SOURCES) +add_library(p3xfileegg STATIC ${P3XFILEEGG_HEADERS} ${P3XFILEEGG_SOURCES}) +target_link_libraries(p3xfileegg p3xfile p3eggbase) + +# This is only needed for binaries in the pandatool package. It is not useful +# for user applications, so it is not installed. diff --git a/pandatool/src/xfileegg/p3xfileegg_composite1.cxx b/pandatool/src/xfileegg/p3xfileegg_composite1.cxx new file mode 100644 index 00000000..d87e0f5b --- /dev/null +++ b/pandatool/src/xfileegg/p3xfileegg_composite1.cxx @@ -0,0 +1,11 @@ + +#include "xFileAnimationSet.cxx" +#include "xFileFace.cxx" +#include "xFileMaker.cxx" +#include "xFileMaterial.cxx" +#include "xFileMesh.cxx" +#include "xFileNormal.cxx" +#include "xFileToEggConverter.cxx" +#include "xFileVertex.cxx" + + diff --git a/pandatool/src/xfileegg/xFileAnimationSet.I b/pandatool/src/xfileegg/xFileAnimationSet.I new file mode 100644 index 00000000..a8501f9c --- /dev/null +++ b/pandatool/src/xfileegg/xFileAnimationSet.I @@ -0,0 +1,46 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileAnimationSet.I + * @author drose + * @date 2004-10-04 + */ + +/** + * + */ +INLINE XFileAnimationSet::FrameEntry:: +FrameEntry() : + _scale(1.0, 1.0, 1.0), + _rot(LQuaterniond::ident_quat()), + _trans(LVector3d::zero()), + _mat(LMatrix4d::ident_mat()) +{ +} + +/** + * Returns the frame's data as a matrix, composing the matrix first if + * necessary, as indicated by the FrameData's _flags member. + */ +INLINE const LMatrix4d &XFileAnimationSet::FrameEntry:: +get_mat(int flags) const { + if ((flags & FDF_mat) == 0) { + ((FrameEntry *)this)->_mat = LMatrix4d::scale_mat(_scale) * _rot; + ((FrameEntry *)this)->_mat.set_row(3, _trans); + } + return _mat; +} + +/** + * + */ +INLINE XFileAnimationSet::FrameData:: +FrameData() : + _flags(0) +{ +} diff --git a/pandatool/src/xfileegg/xFileAnimationSet.cxx b/pandatool/src/xfileegg/xFileAnimationSet.cxx new file mode 100644 index 00000000..f76bc33e --- /dev/null +++ b/pandatool/src/xfileegg/xFileAnimationSet.cxx @@ -0,0 +1,153 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileAnimationSet.cxx + * @author drose + * @date 2004-10-02 + */ + +#include "xFileAnimationSet.h" +#include "xFileToEggConverter.h" +#include "config_xfile.h" +#include "eggGroup.h" +#include "eggTable.h" +#include "eggData.h" +#include "eggXfmSAnim.h" +#include "dcast.h" + +/** + * + */ +XFileAnimationSet:: +XFileAnimationSet() { + _frame_rate = 0.0; +} + +/** + * + */ +XFileAnimationSet:: +~XFileAnimationSet() { +} + +/** + * Sets up the hierarchy of EggTables corresponding to this AnimationSet. + */ +bool XFileAnimationSet:: +create_hierarchy(XFileToEggConverter *converter) { + // Egg animation tables start off with one Table entry, enclosing a Bundle + // entry. + EggTable *table = new EggTable(get_name()); + converter->get_egg_data()->add_child(table); + EggTable *bundle = new EggTable(converter->_char_name); + table->add_child(bundle); + bundle->set_table_type(EggTable::TT_bundle); + + // Then the Bundle contains a "" entry, which begins the animation + // table hierarchy. + EggTable *skeleton = new EggTable(""); + bundle->add_child(skeleton); + + // Fill in the rest of the hierarchy with empty tables. + mirror_table(converter, converter->get_dart_node(), skeleton); + + // Now populate those empty tables with the frame data. + JointData::const_iterator ji; + for (ji = _joint_data.begin(); ji != _joint_data.end(); ++ji) { + const std::string &joint_name = (*ji).first; + const FrameData &table = (*ji).second; + + EggXfmSAnim *anim_table = get_table(joint_name); + if (anim_table == nullptr) { + xfile_cat.warning() + << "Frame " << joint_name << ", named by animation data, not defined.\n"; + } else { + // If we have animation data, apply it. + FrameEntries::const_iterator fi; + for (fi = table._entries.begin(); fi != table._entries.end(); ++fi) { + anim_table->add_data((*fi).get_mat(table._flags)); + } + anim_table->optimize(); + } + } + + // Put some data in the empty tables also. + Tables::iterator ti; + for (ti = _tables.begin(); ti != _tables.end(); ++ti) { + EggXfmSAnim *anim_table = (*ti).second._table; + EggGroup *joint = (*ti).second._joint; + if (anim_table->empty() && joint != nullptr) { + // If there's no animation data, assign the rest transform. + anim_table->add_data(joint->get_transform3d()); + } + anim_table->optimize(); + if (_frame_rate != 0.0) { + anim_table->set_fps(_frame_rate); + } + } + + return true; +} + +/** + * Returns the table associated with the indicated joint name. + */ +EggXfmSAnim *XFileAnimationSet:: +get_table(const std::string &joint_name) const { + Tables::const_iterator ti; + ti = _tables.find(joint_name); + if (ti != _tables.end()) { + return (*ti).second._table; + } + return nullptr; +} + +/** + * Returns a reference to a new FrameData table corresponding to the indicated + * joint. + */ +XFileAnimationSet::FrameData &XFileAnimationSet:: +create_frame_data(const std::string &joint_name) { + return _joint_data[joint_name]; +} + +/** + * Builds up a new set of EggTable nodes, as a mirror of the existing set of + * EggGroup (joint) nodes, and saves each new table in the _tables record. + */ +void XFileAnimationSet:: +mirror_table(XFileToEggConverter *converter, + EggGroup *model_node, EggTable *anim_node) { + EggGroupNode::iterator gi; + for (gi = model_node->begin(); gi != model_node->end(); ++gi) { + EggNode *child = (*gi); + if (child->is_of_type(EggGroup::get_class_type())) { + EggGroup *group = DCAST(EggGroup, child); + if (group->get_group_type() == EggGroup::GT_joint) { + // When we come to a , create a new Table for it. + EggTable *new_table = new EggTable(group->get_name()); + anim_node->add_child(new_table); + CoordinateSystem cs = + converter->get_egg_data()->get_coordinate_system(); + EggXfmSAnim *xform = new EggXfmSAnim("xform", cs); + new_table->add_child(xform); + xform->set_fps(converter->_frame_rate); + TablePair &table_pair = _tables[group->get_name()]; + table_pair._table = xform; + table_pair._joint = group; + + // Now recurse. + mirror_table(converter, group, new_table); + + } else { + // If we come to an ordinary , skip past it. + mirror_table(converter, group, anim_node); + } + } + } +} diff --git a/pandatool/src/xfileegg/xFileAnimationSet.h b/pandatool/src/xfileegg/xFileAnimationSet.h new file mode 100644 index 00000000..a0bec7d4 --- /dev/null +++ b/pandatool/src/xfileegg/xFileAnimationSet.h @@ -0,0 +1,92 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileAnimationSet.h + * @author drose + * @date 2004-10-02 + */ + +#ifndef XFILEANIMATIONSET_H +#define XFILEANIMATIONSET_H + +#include "pandatoolbase.h" +#include "pmap.h" +#include "epvector.h" +#include "luse.h" +#include "namable.h" + +class XFileToEggConverter; +class EggGroup; +class EggTable; +class EggXfmSAnim; + +/** + * This represents a tree of EggTables, corresponding to Animation entries in + * the X file. There is one EggTable for each joint in the character's joint + * set, and the whole tree is structured as a mirror of the joint set. + */ +class XFileAnimationSet : public Namable { +public: + XFileAnimationSet(); + ~XFileAnimationSet(); + + bool create_hierarchy(XFileToEggConverter *converter); + EggXfmSAnim *get_table(const std::string &joint_name) const; + + enum FrameDataFlags { + FDF_scale = 0x01, + FDF_rot = 0x02, + FDF_trans = 0x04, + FDF_mat = 0x08, + }; + + class FrameEntry { + public: + INLINE FrameEntry(); + INLINE const LMatrix4d &get_mat(int flags) const; + + LVecBase3d _scale; + LQuaterniond _rot; + LVector3d _trans; + LMatrix4d _mat; + }; + + typedef epvector FrameEntries; + + class FrameData { + public: + INLINE FrameData(); + FrameEntries _entries; + int _flags; + }; + + FrameData &create_frame_data(const std::string &joint_name); + +public: + double _frame_rate; + +private: + void mirror_table(XFileToEggConverter *converter, + EggGroup *model_node, EggTable *anim_node); + + typedef pmap JointData; + JointData _joint_data; + + class TablePair { + public: + EggGroup *_joint; + EggXfmSAnim *_table; + }; + + typedef pmap Tables; + Tables _tables; +}; + +#include "xFileAnimationSet.I" + +#endif diff --git a/pandatool/src/xfileegg/xFileFace.cxx b/pandatool/src/xfileegg/xFileFace.cxx new file mode 100644 index 00000000..ecb1378c --- /dev/null +++ b/pandatool/src/xfileegg/xFileFace.cxx @@ -0,0 +1,43 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileFace.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileFace.h" +#include "xFileMesh.h" +#include "eggPolygon.h" + +/** + * + */ +XFileFace:: +XFileFace() { + _material_index = -1; +} + +/** + * Sets the structure up from the indicated egg data. + */ +void XFileFace:: +set_from_egg(XFileMesh *mesh, EggPolygon *egg_poly) { + // Walk through the polygon's vertices in reverse order, to change from + // Egg's counter-clockwise convention to DX's clockwise. + EggPolygon::reverse_iterator vi; + for (vi = egg_poly->rbegin(); vi != egg_poly->rend(); ++vi) { + EggVertex *egg_vertex = (*vi); + Vertex v; + v._vertex_index = mesh->add_vertex(egg_vertex, egg_poly); + v._normal_index = mesh->add_normal(egg_vertex, egg_poly); + _vertices.push_back(v); + } + + _material_index = mesh->add_material(egg_poly); +} diff --git a/pandatool/src/xfileegg/xFileFace.h b/pandatool/src/xfileegg/xFileFace.h new file mode 100644 index 00000000..788d664e --- /dev/null +++ b/pandatool/src/xfileegg/xFileFace.h @@ -0,0 +1,42 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileFace.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEFACE_H +#define XFILEFACE_H + +#include "pandatoolbase.h" +#include "pvector.h" + +class XFileMesh; +class EggPolygon; + +/** + * This represents a single face of an XFileMesh. + */ +class XFileFace { +public: + XFileFace(); + void set_from_egg(XFileMesh *mesh, EggPolygon *egg_poly); + + class Vertex { + public: + int _vertex_index; + int _normal_index; + }; + typedef pvector Vertices; + Vertices _vertices; + + int _material_index; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileMaker.cxx b/pandatool/src/xfileegg/xFileMaker.cxx new file mode 100644 index 00000000..e3d07cea --- /dev/null +++ b/pandatool/src/xfileegg/xFileMaker.cxx @@ -0,0 +1,243 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMaker.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileMaker.h" +#include "xFileMesh.h" +#include "xFileMaterial.h" +#include "config_xfile.h" + +#include "pnotify.h" +#include "eggGroupNode.h" +#include "eggGroup.h" +#include "eggBin.h" +#include "eggPolysetMaker.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggPolygon.h" +#include "eggData.h" +#include "pvector.h" +#include "vector_int.h" +#include "string_utils.h" +#include "datagram.h" + +/** + * + */ +XFileMaker:: +XFileMaker() { + _mesh_index = 0; + _x_file = new XFile; +} + +/** + * + */ +XFileMaker:: +~XFileMaker() { +} + +/** + * Writes the .x file data to the indicated filename; returns true on success, + * false otherwise. + */ +bool XFileMaker:: +write(const Filename &filename) { + return _x_file->write(filename); +} + +/** + * Adds the egg tree rooted at the indicated node to the X structure. This + * may be somewhat destructive of the egg tree. Returns true on success, + * false on failure. + */ +bool XFileMaker:: +add_tree(EggData *egg_data) { + _meshes.clear(); + + // Now collect all the polygons together into polysets. + EggPolysetMaker pmaker; + pmaker.make_bins(egg_data); + + // And now we're ready to traverse the egg hierarchy. + if (!recurse_nodes(egg_data, _x_file)) { + return false; + } + + // Create X structures for all of the meshes we built up. + Meshes::iterator mi; + for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) { + if (!finalize_mesh((*mi).first, (*mi).second)) { + return false; + } + } + _meshes.clear(); + + return true; +} + +/** + * Adds the node to the DX structure, in whatever form it is supported. + */ +bool XFileMaker:: +add_node(EggNode *egg_node, XFileNode *x_parent) { + if (egg_node->is_of_type(EggBin::get_class_type())) { + return add_bin(DCAST(EggBin, egg_node), x_parent); + + } else if (egg_node->is_of_type(EggGroup::get_class_type())) { + return add_group(DCAST(EggGroup, egg_node), x_parent); + + } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) { + // A grouping node of some kind. + EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node); + + if (xfile_one_mesh) { + // Don't create any additional frames representing the egg hierarchy. + if (!recurse_nodes(egg_group, x_parent)) { + return false; + } + + } else { + // Create a Frame for each EggGroup. + XFileDataNode *x_frame = x_parent->add_Frame(egg_group->get_name()); + + if (!recurse_nodes(egg_group, x_frame)) { + return false; + } + } + + return true; + } + + // Some unsupported node type. Ignore it. + return true; +} + +/** + * Adds a frame for the indicated group node. + */ +bool XFileMaker:: +add_group(EggGroup *egg_group, XFileNode *x_parent) { + if (xfile_one_mesh) { + // Don't create any additional frames representing the egg hierarchy. + if (!recurse_nodes(egg_group, x_parent)) { + return false; + } + + } else { + // Create a frame for each EggGroup. + XFileDataNode *x_frame = x_parent->add_Frame(egg_group->get_name()); + + // Set the transform on the frame, if we have one. + if (egg_group->has_transform()) { + x_frame->add_FrameTransformMatrix(egg_group->get_transform3d()); + } + + if (!recurse_nodes(egg_group, x_frame)) { + return false; + } + } + + return true; +} + +/** + * Determines what kind of object needs to be added for the indicated bin + * node. + */ +bool XFileMaker:: +add_bin(EggBin *egg_bin, XFileNode *x_parent) { + switch (egg_bin->get_bin_number()) { + case EggPolysetMaker::BN_polyset: + return add_polyset(egg_bin, x_parent); + } + + xfile_cat.error() + << "Unexpected bin type " << egg_bin->get_bin_number() << "\n"; + return false; +} + +/** + * Adds a mesh object corresponding to the collection of polygons within the + * indicated bin. + */ +bool XFileMaker:: +add_polyset(EggBin *egg_bin, XFileNode *x_parent) { + // Make sure that all our polygons are reasonable. + egg_bin->remove_invalid_primitives(true); + + XFileMesh *mesh = get_mesh(x_parent); + + EggGroupNode::iterator ci; + for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) { + EggPolygon *poly; + DCAST_INTO_R(poly, *ci, false); + + mesh->add_polygon(poly); + } + + return true; +} + + +/** + * Adds each child of the indicated Node as a child of the indicated DX + * object. + */ +bool XFileMaker:: +recurse_nodes(EggGroupNode *egg_node, XFileNode *x_parent) { + EggGroupNode::iterator ci; + for (ci = egg_node->begin(); ci != egg_node->end(); ++ci) { + EggNode *child = (*ci); + if (!add_node(child, x_parent)) { + return false; + } + } + + return true; +} + +/** + * Returns a suitable XFileMesh object for creating meshes within the + * indicated x_parent object. + */ +XFileMesh *XFileMaker:: +get_mesh(XFileNode *x_parent) { + Meshes::iterator mi = _meshes.find(x_parent); + if (mi != _meshes.end()) { + // We've already started working on this x_parent before; use the same + // mesh object. + return (*mi).second; + } + + // We haven't seen this x_parent before; create a new mesh object. + XFileMesh *mesh = new XFileMesh; + _meshes.insert(Meshes::value_type(x_parent, mesh)); + return mesh; +} + + +/** + * Creates the actual X structures corresponding to the indicated XFileMesh + * object. + */ +bool XFileMaker:: +finalize_mesh(XFileNode *x_parent, XFileMesh *mesh) { + // Get a unique number for each mesh. + _mesh_index++; + std::string mesh_index = format_string(_mesh_index); + + // Finally, create the Mesh object. + mesh->make_x_mesh(x_parent, mesh_index); + + return true; +} diff --git a/pandatool/src/xfileegg/xFileMaker.h b/pandatool/src/xfileegg/xFileMaker.h new file mode 100644 index 00000000..18ba2a99 --- /dev/null +++ b/pandatool/src/xfileegg/xFileMaker.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMaker.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEMAKER_H +#define XFILEMAKER_H + +#include "pandatoolbase.h" + +#include "filename.h" +#include "pmap.h" +#include "luse.h" +#include "xFile.h" + +class EggNode; +class EggGroupNode; +class EggGroup; +class EggBin; +class EggData; +class EggVertexPool; +class Datagram; +class XFileMesh; + +/** + * This class converts a Panda scene graph into a .X file and writes it out. + */ +class XFileMaker { +public: + XFileMaker(); + ~XFileMaker(); + + bool write(const Filename &filename); + + bool add_tree(EggData *egg_data); + +private: + bool add_node(EggNode *egg_node, XFileNode *x_parent); + bool add_group(EggGroup *egg_group, XFileNode *x_parent); + bool add_bin(EggBin *egg_bin, XFileNode *x_parent); + bool add_polyset(EggBin *egg_bin, XFileNode *x_parent); + + bool recurse_nodes(EggGroupNode *egg_node, XFileNode *x_parent); + + XFileMesh *get_mesh(XFileNode *x_parent); + bool finalize_mesh(XFileNode *x_parent, XFileMesh *mesh); + + PT(XFile) _x_file; + + int _mesh_index; + + typedef pmap Meshes; + Meshes _meshes; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileMaterial.cxx b/pandatool/src/xfileegg/xFileMaterial.cxx new file mode 100644 index 00000000..9eaac25f --- /dev/null +++ b/pandatool/src/xfileegg/xFileMaterial.cxx @@ -0,0 +1,209 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMaterial.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileMaterial.h" +#include "xFileToEggConverter.h" +#include "xFileDataNode.h" +#include "eggMaterial.h" +#include "eggTexture.h" +#include "eggPrimitive.h" +#include "datagram.h" +#include "config_xfile.h" + +#include // for strcmp, strdup + +/** + * + */ +XFileMaterial:: +XFileMaterial() { + _face_color.set(1.0, 1.0, 1.0, 1.0); + _specular_color.set(0.0, 0.0, 0.0); + _emissive_color.set(0.0, 0.0, 0.0); + _power = 64.0; + + _has_material = false; + _has_texture = false; +} + +/** + * + */ +XFileMaterial:: +~XFileMaterial() { +} + +/** + * Sets the structure up from the indicated egg data. + */ +void XFileMaterial:: +set_from_egg(EggPrimitive *egg_prim) { + // First, determine the face color. + if (egg_prim->has_color()) { + _face_color = egg_prim->get_color(); + _has_material = true; + } + + // Do we have an actual EggMaterial, to control lighting effects? + if (egg_prim->has_material()) { + _has_material = true; + EggMaterial *egg_mat = egg_prim->get_material(); + if (egg_mat->has_diff()) { + _face_color = egg_mat->get_diff(); + } + if (egg_mat->has_spec()) { + const LColor &spec = egg_mat->get_spec(); + _specular_color.set(spec[0], spec[1], spec[2]); + } + if (egg_mat->has_emit()) { + const LColor &emit = egg_mat->get_emit(); + _emissive_color.set(emit[0], emit[1], emit[2]); + } + if (egg_mat->has_shininess()) { + _power = egg_mat->get_shininess(); + } + } + + // Finally, if we also have a texture, record that. + if (egg_prim->has_texture()) { + _has_material = true; + _has_texture = true; + EggTexture *egg_tex = egg_prim->get_texture(); + _texture = egg_tex->get_filename(); + } +} + +/** + * Applies the properties in the material to the indicated egg primitive. + */ +void XFileMaterial:: +apply_to_egg(EggPrimitive *egg_prim, XFileToEggConverter *converter) { + // Is there a texture? + if (_has_texture) { + Filename texture = converter->convert_model_path(_texture); + EggTexture temp("", texture); + EggTexture *egg_tex = converter->create_unique_texture(temp); + egg_prim->set_texture(egg_tex); + } + + // Are there any fancy lighting effects? + bool got_spec = (_specular_color != LRGBColor::zero()); + bool got_emit = (_emissive_color != LRGBColor::zero()); + if (got_spec || got_emit) { + EggMaterial temp(""); + temp.set_diff(_face_color); + if (got_spec) { + temp.set_shininess(_power); + temp.set_spec(LColor(_specular_color[0], _specular_color[1], + _specular_color[2], 1.0)); + } + if (got_emit) { + temp.set_emit(LColor(_emissive_color[0], _emissive_color[1], + _emissive_color[2], 1.0)); + } + EggMaterial *egg_mat = converter->create_unique_material(temp); + egg_prim->set_material(egg_mat); + } + + // Also apply the face color. + egg_prim->set_color(_face_color); +} + +/** + * + */ +int XFileMaterial:: +compare_to(const XFileMaterial &other) const { + int ct; + ct = _face_color.compare_to(other._face_color); + if (ct == 0) { + ct = (_power == other._power) ? 0 : ((_power < other._power) ? -1 : 1); + } + if (ct == 0) { + ct = _specular_color.compare_to(other._specular_color); + } + if (ct == 0) { + ct = _emissive_color.compare_to(other._emissive_color); + } + if (ct == 0) { + ct = strcmp(_texture.c_str(), other._texture.c_str()); + } + return ct; +} + +/** + * Returns true if this material represents something meaningful, or false if + * the default material is sufficient. + */ +bool XFileMaterial:: +has_material() const { + return _has_material; +} + +/** + * Returns true if this material includes a texture map, false otherwise. + */ +bool XFileMaterial:: +has_texture() const { + return _has_texture; +} + +/** + * Creates a Material object for the material list. + */ +XFileDataNode *XFileMaterial:: +make_x_material(XFileNode *x_meshMaterials, const std::string &suffix) { + XFileDataNode *x_material = + x_meshMaterials->add_Material("material" + suffix, + _face_color, _power, + _specular_color, _emissive_color); + + if (has_texture()) { + x_material->add_TextureFilename("texture" + suffix, _texture); + } + + return x_material; +} + +/** + * Fills the structure based on the raw data from the X file's Material + * object. + */ +bool XFileMaterial:: +fill_material(XFileDataNode *obj) { + _face_color = LCAST(PN_stdfloat, (*obj)["faceColor"].vec4()); + _power = (*obj)["power"].d(); + _specular_color = LCAST(PN_stdfloat, (*obj)["specularColor"].vec3()); + _emissive_color = LCAST(PN_stdfloat, (*obj)["emissiveColor"].vec3()); + _has_material = true; + + // Walk through the children of the material. If there are any, there + // should be only one, and it should be just a Texture. + int num_objects = obj->get_num_objects(); + for (int i = 0; i < num_objects; i++) { + XFileDataNode *child = obj->get_object(i); + if (child->is_standard_object("TextureFilename")) { + _texture = Filename::from_os_specific((*child)["filename"].s()); + _has_texture = true; + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring material object of unknown type: " + << child->get_template_name() << "\n"; + } + } + } + + return true; +} diff --git a/pandatool/src/xfileegg/xFileMaterial.h b/pandatool/src/xfileegg/xFileMaterial.h new file mode 100644 index 00000000..dc6a586d --- /dev/null +++ b/pandatool/src/xfileegg/xFileMaterial.h @@ -0,0 +1,58 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMaterial.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEMATERIAL_H +#define XFILEMATERIAL_H + +#include "pandatoolbase.h" +#include "luse.h" +#include "filename.h" + +class EggPrimitive; +class Datagram; +class XFileToEggConverter; +class XFileNode; +class XFileDataNode; + +/** + * This represents an X file "material", which consists of a color, lighting, + * and/or texture specification. + */ +class XFileMaterial { +public: + XFileMaterial(); + ~XFileMaterial(); + + void set_from_egg(EggPrimitive *egg_prim); + void apply_to_egg(EggPrimitive *egg_prim, XFileToEggConverter *converter); + + int compare_to(const XFileMaterial &other) const; + + bool has_material() const; + bool has_texture() const; + + XFileDataNode *make_x_material(XFileNode *x_meshMaterials, const std::string &suffix); + bool fill_material(XFileDataNode *obj); + +private: + LColor _face_color; + double _power; + LRGBColor _specular_color; + LRGBColor _emissive_color; + Filename _texture; + + bool _has_material; + bool _has_texture; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileMesh.cxx b/pandatool/src/xfileegg/xFileMesh.cxx new file mode 100644 index 00000000..e253ee38 --- /dev/null +++ b/pandatool/src/xfileegg/xFileMesh.cxx @@ -0,0 +1,884 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMesh.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileMesh.h" +#include "xFileToEggConverter.h" +#include "xFileFace.h" +#include "xFileVertex.h" +#include "xFileNormal.h" +#include "xFileMaterial.h" +#include "xFileDataNode.h" +#include "config_xfile.h" +#include "string_utils.h" +#include "eggVertexPool.h" +#include "eggVertex.h" +#include "eggPolygon.h" +#include "eggGroup.h" +#include "eggGroupNode.h" + +using std::min; +using std::string; + +/** + * + */ +XFileMesh:: +XFileMesh(CoordinateSystem cs) : _cs(cs) { + _has_normals = false; + _has_colors = false; + _has_uvs = false; + _has_materials = false; + _egg_parent = nullptr; +} + +/** + * + */ +XFileMesh:: +~XFileMesh() { + clear(); +} + +/** + * Empties all data from the mesh. + */ +void XFileMesh:: +clear() { + Vertices::iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + XFileVertex *vertex = (*vi); + delete vertex; + } + Normals::iterator ni; + for (ni = _normals.begin(); ni != _normals.end(); ++ni) { + XFileNormal *normal = (*ni); + delete normal; + } + Materials::iterator mi; + for (mi = _materials.begin(); mi != _materials.end(); ++mi) { + XFileMaterial *material = (*mi); + delete material; + } + Faces::iterator fi; + for (fi = _faces.begin(); fi != _faces.end(); ++fi) { + XFileFace *face = (*fi); + delete face; + } + + _vertices.clear(); + _normals.clear(); + _materials.clear(); + _faces.clear(); + + _unique_vertices.clear(); + _unique_normals.clear(); + _unique_materials.clear(); + + _has_normals = false; + _has_colors = false; + _has_uvs = false; + _has_materials = false; +} + +/** + * Adds the indicated polygon to the mesh. + */ +void XFileMesh:: +add_polygon(EggPolygon *egg_poly) { + XFileFace *face = new XFileFace; + face->set_from_egg(this, egg_poly); + _faces.push_back(face); +} + +/** + * Creates a new XFileVertex, if one does not already exist for the indicated + * vertex, and returns its index. + */ +int XFileMesh:: +add_vertex(EggVertex *egg_vertex, EggPrimitive *egg_prim) { + int next_index = _vertices.size(); + XFileVertex *vertex = new XFileVertex; + vertex->set_from_egg(egg_vertex, egg_prim); + if (vertex->_has_color) { + _has_colors = true; + } + if (vertex->_has_uv) { + _has_uvs = true; + } + + std::pair result = + _unique_vertices.insert(UniqueVertices::value_type(vertex, next_index)); + + if (result.second) { + // Successfully added; this is a new vertex. + _vertices.push_back(vertex); + return next_index; + } else { + // Not successfully added; there is already a vertex with these + // properties. Return that one instead. + delete vertex; + return (*result.first).second; + } +} + + +/** + * Creates a new XFileNormal, if one does not already exist for the indicated + * normal, and returns its index. + */ +int XFileMesh:: +add_normal(EggVertex *egg_vertex, EggPrimitive *egg_prim) { + int next_index = _normals.size(); + XFileNormal *normal = new XFileNormal; + normal->set_from_egg(egg_vertex, egg_prim); + if (normal->_has_normal) { + _has_normals = true; + } + + std::pair result = + _unique_normals.insert(UniqueNormals::value_type(normal, next_index)); + + if (result.second) { + // Successfully added; this is a new normal. + _normals.push_back(normal); + return next_index; + } else { + // Not successfully added; there is already a normal with these + // properties. Return that one instead. + delete normal; + return (*result.first).second; + } +} + +/** + * Creates a new XFileMaterial, if one does not already exist for the + * indicated material, and returns its index. + */ +int XFileMesh:: +add_material(EggPrimitive *egg_prim) { + int next_index = _materials.size(); + XFileMaterial *material = new XFileMaterial; + material->set_from_egg(egg_prim); + if (material->has_material()) { + _has_materials = true; + } + + std::pair result = + _unique_materials.insert(UniqueMaterials::value_type(material, next_index)); + + if (result.second) { + // Successfully added; this is a new material. + _materials.push_back(material); + return next_index; + } else { + // Not successfully added; there is already a material with these + // properties. Return that one instead. + delete material; + return (*result.first).second; + } +} + + +/** + * Adds the newly-created XFileVertex unequivocally to the mesh, returning its + * index number. The XFileMesh object becomes the owner of the XFileVertex + * pointer, and will delete it when it destructs. + */ +int XFileMesh:: +add_vertex(XFileVertex *vertex) { + if (vertex->_has_color) { + _has_colors = true; + } + if (vertex->_has_uv) { + _has_uvs = true; + } + + int next_index = _vertices.size(); + _unique_vertices.insert(UniqueVertices::value_type(vertex, next_index)); + _vertices.push_back(vertex); + return next_index; +} + +/** + * Adds the newly-created XFileNormal unequivocally to the mesh, returning its + * index number. The XFileMesh object becomes the owner of the XFileNormal + * pointer, and will delete it when it destructs. + */ +int XFileMesh:: +add_normal(XFileNormal *normal) { + if (normal->_has_normal) { + _has_normals = true; + } + + int next_index = _normals.size(); + _unique_normals.insert(UniqueNormals::value_type(normal, next_index)); + _normals.push_back(normal); + return next_index; +} + +/** + * Adds the newly-created XFileMaterial unequivocally to the mesh, returning + * its index number. The XFileMesh object becomes the owner of the + * XFileMaterial pointer, and will delete it when it destructs. + */ +int XFileMesh:: +add_material(XFileMaterial *material) { + if (material->has_material()) { + _has_materials = true; + } + + int next_index = _materials.size(); + _unique_materials.insert(UniqueMaterials::value_type(material, next_index)); + _materials.push_back(material); + return next_index; +} + +/** + * Specifies the egg node that will eventually be the parent of this mesh, + * when create_polygons() is later called. + */ +void XFileMesh:: +set_egg_parent(EggGroupNode *egg_parent) { + // We actually put the mesh under its own group. + EggGroup *egg_group = new EggGroup(get_name()); + egg_parent->add_child(egg_group); + + _egg_parent = egg_group; +} + +/** + * Creates a slew of EggPolygons according to the faces in the mesh, and adds + * them to the previously-indicated parent node. + */ +bool XFileMesh:: +create_polygons(XFileToEggConverter *converter) { + nassertr(_egg_parent != nullptr, false); + + EggVertexPool *vpool = new EggVertexPool(get_name()); + _egg_parent->add_child(vpool); + Faces::const_iterator fi; + for (fi = _faces.begin(); fi != _faces.end(); ++fi) { + XFileFace *face = (*fi); + + EggPolygon *egg_poly = new EggPolygon; + _egg_parent->add_child(egg_poly); + + // Set up the vertices for the polygon. + XFileFace::Vertices::reverse_iterator vi; + for (vi = face->_vertices.rbegin(); vi != face->_vertices.rend(); ++vi) { + int vertex_index = (*vi)._vertex_index; + int normal_index = (*vi)._normal_index; + if (vertex_index < 0 || vertex_index >= (int)_vertices.size()) { + xfile_cat.warning() + << "Vertex index out of range in Mesh " << get_name() << "\n"; + continue; + } + XFileVertex *vertex = _vertices[vertex_index]; + XFileNormal *normal = nullptr; + + if (normal_index >= 0 && normal_index < (int)_normals.size()) { + normal = _normals[normal_index]; + } + + // Create a temporary EggVertex before adding it to the pool. + EggVertex temp_vtx; + temp_vtx.set_external_index(vertex_index); + temp_vtx.set_pos(vertex->_point); + if (vertex->_has_color) { + temp_vtx.set_color(vertex->_color); + } + if (vertex->_has_uv) { + LTexCoordd uv = vertex->_uv; + // Windows draws the UV's upside-down. + uv[1] = 1.0 - uv[1]; + temp_vtx.set_uv(uv); + } + + if (normal != nullptr && normal->_has_normal) { + temp_vtx.set_normal(normal->_normal); + } + + // We are given the vertex in local space; we need to transform it into + // global space. If the vertex has been skinned, that means the global + // space of all of its joints (modified by the matrix_offset provided in + // the skinning data). + double net_weight = 0.0; + LMatrix4d weighted_transform(0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + SkinWeights::const_iterator swi; + for (swi = _skin_weights.begin(); swi != _skin_weights.end(); ++swi) { + const SkinWeightsData &data = (*swi); + WeightMap::const_iterator wmi = data._weight_map.find(vertex_index); + if (wmi != data._weight_map.end()) { + EggGroup *joint = converter->find_joint(data._joint_name); + if (joint != nullptr) { + double weight = (*wmi).second; + LMatrix4d mat = data._matrix_offset; + mat *= joint->get_node_to_vertex(); + weighted_transform += mat * weight; + net_weight += weight; + } + } + } + + if (net_weight == 0.0) { + // The vertex had no joint membership. Transform it into the + // appropriate (global) space based on its parent. + temp_vtx.transform(_egg_parent->get_node_to_vertex()); + + } else { + // The vertex was skinned into one or more joints. Therefore, + // transform it according to the blended matrix_offset from the + // skinning data. + weighted_transform /= net_weight; + temp_vtx.transform(weighted_transform); + } + + // Now get a real EggVertex matching our template. + EggVertex *egg_vtx = vpool->create_unique_vertex(temp_vtx); + egg_poly->add_vertex(egg_vtx); + } + + // And apply the material for the polygon. + int material_index = face->_material_index; + if (material_index >= 0 && material_index < (int)_materials.size()) { + XFileMaterial *material = _materials[material_index]; + material->apply_to_egg(egg_poly, converter); + } + } + + // Now go through all of the vertices and skin them up. + EggVertexPool::iterator vi; + for (vi = vpool->begin(); vi != vpool->end(); ++vi) { + EggVertex *egg_vtx = (*vi); + int vertex_index = egg_vtx->get_external_index(); + + SkinWeights::const_iterator swi; + for (swi = _skin_weights.begin(); swi != _skin_weights.end(); ++swi) { + const SkinWeightsData &data = (*swi); + WeightMap::const_iterator wmi = data._weight_map.find(vertex_index); + if (wmi != data._weight_map.end()) { + EggGroup *joint = converter->find_joint(data._joint_name); + if (joint != nullptr) { + double weight = (*wmi).second; + joint->ref_vertex(egg_vtx, weight); + } + } + } + } + + if (!has_normals()) { + // If we don't have explicit normals, make some up, per the DX spec. + // Since the DX spec doesn't mention anything about a crease angle, we + // should be as generous as possible. + _egg_parent->recompute_vertex_normals(180.0, _cs); + } + + return true; +} + +/** + * Returns true if any of the vertices or faces added to this mesh used a + * normal, false otherwise. + */ +bool XFileMesh:: +has_normals() const { + return _has_normals; +} + +/** + * Returns true if any of the vertices or faces added to this mesh used a + * color, false otherwise. + */ +bool XFileMesh:: +has_colors() const { + return _has_colors; +} + +/** + * Returns true if any of the vertices added to this mesh used a texture + * coordinate, false otherwise. + */ +bool XFileMesh:: +has_uvs() const { + return _has_uvs; +} + +/** + * Returns true if any of the faces added to this mesh used a real material, + * false otherwise. + */ +bool XFileMesh:: +has_materials() const { + return _has_materials; +} + +/** + * Returns the number of distinct materials associated with the mesh. + */ +int XFileMesh:: +get_num_materials() const { + return _materials.size(); +} + +/** + * Returns a pointer to the nth materials associated with the mesh. + */ +XFileMaterial *XFileMesh:: +get_material(int n) const { + nassertr(n >= 0 && n < (int)_materials.size(), nullptr); + return _materials[n]; +} + +/** + * Creates an X structure corresponding to the mesh. + */ +XFileDataNode *XFileMesh:: +make_x_mesh(XFileNode *x_parent, const string &suffix) { + XFileDataNode *x_mesh = x_parent->add_Mesh("mesh" + suffix); + + // First, fill in the table of vertices. + XFileDataObject &x_vertices = (*x_mesh)["vertices"]; + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + XFileVertex *vertex = (*vi); + x_vertices.add_Vector(x_mesh->get_x_file(), vertex->_point); + } + (*x_mesh)["nVertices"] = x_vertices.size(); + + // Then, create the list of faces that index into the above vertices. + XFileDataObject &x_faces = (*x_mesh)["faces"]; + Faces::const_iterator fi; + for (fi = _faces.begin(); fi != _faces.end(); ++fi) { + XFileFace *face = (*fi); + + XFileDataObject &x_mesh_face = x_faces.add_MeshFace(x_mesh->get_x_file()); + XFileDataObject &x_faceVertexIndices = x_mesh_face["faceVertexIndices"]; + XFileFace::Vertices::const_iterator fvi; + for (fvi = face->_vertices.begin(); + fvi != face->_vertices.end(); + ++fvi) { + x_faceVertexIndices.add_int((*fvi)._vertex_index); + } + x_mesh_face["nFaceVertexIndices"] = x_faceVertexIndices.size(); + } + (*x_mesh)["nFaces"] = x_faces.size(); + + // Now, add in any supplemental data. + + if (has_normals()) { + // Tack on normals. + make_x_normals(x_mesh, suffix); + } + if (has_colors()) { + // Tack on colors. + make_x_colors(x_mesh, suffix); + } + if (has_uvs()) { + // Tack on uvs. + make_x_uvs(x_mesh, suffix); + } + if (has_materials()) { + // Tack on materials. + make_x_material_list(x_mesh, suffix); + } + + return x_mesh; +} + +/** + * Creates a MeshNormals table for the mesh. + */ +XFileDataNode *XFileMesh:: +make_x_normals(XFileNode *x_mesh, const string &suffix) { + XFileDataNode *x_meshNormals = x_mesh->add_MeshNormals("norms" + suffix); + + XFileDataObject &x_normals = (*x_meshNormals)["normals"]; + + Normals::const_iterator ni; + for (ni = _normals.begin(); ni != _normals.end(); ++ni) { + XFileNormal *normal = (*ni); + x_normals.add_Vector(x_mesh->get_x_file(), normal->_normal); + } + (*x_meshNormals)["nNormals"] = x_normals.size(); + + // Then, create the list of faces that index into the above normals. + XFileDataObject &x_faces = (*x_meshNormals)["faceNormals"]; + Faces::const_iterator fi; + for (fi = _faces.begin(); fi != _faces.end(); ++fi) { + XFileFace *face = (*fi); + + XFileDataObject &x_normals_face = x_faces.add_MeshFace(x_mesh->get_x_file()); + XFileDataObject &x_faceVertexIndices = x_normals_face["faceVertexIndices"]; + XFileFace::Vertices::const_iterator fvi; + for (fvi = face->_vertices.begin(); + fvi != face->_vertices.end(); + ++fvi) { + x_faceVertexIndices.add_int((*fvi)._normal_index); + } + x_normals_face["nFaceVertexIndices"] = x_faceVertexIndices.size(); + } + (*x_meshNormals)["nFaceNormals"] = x_faces.size(); + + return x_meshNormals; +} + +/** + * Creates a MeshVertexColors table for the mesh. + */ +XFileDataNode *XFileMesh:: +make_x_colors(XFileNode *x_mesh, const string &suffix) { + XFileDataNode *x_meshColors = x_mesh->add_MeshVertexColors("colors" + suffix); + + XFileDataObject &x_colors = (*x_meshColors)["vertexColors"]; + + Vertices::const_iterator vi; + int i = 0; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + XFileVertex *vertex = (*vi); + const LColor &color = vertex->_color; + x_colors.add_IndexedColor(x_mesh->get_x_file(), i, color); + i++; + } + + (*x_meshColors)["nVertexColors"] = x_colors.size(); + + return x_meshColors; +} + +/** + * Creates a MeshTextureCoords table for the mesh. + */ +XFileDataNode *XFileMesh:: +make_x_uvs(XFileNode *x_mesh, const string &suffix) { + XFileDataNode *x_meshUvs = x_mesh->add_MeshTextureCoords("uvs" + suffix); + + XFileDataObject &x_uvs = (*x_meshUvs)["textureCoords"]; + + Vertices::const_iterator vi; + for (vi = _vertices.begin(); vi != _vertices.end(); ++vi) { + XFileVertex *vertex = (*vi); + x_uvs.add_Coords2d(x_mesh->get_x_file(), vertex->_uv); + } + + (*x_meshUvs)["nTextureCoords"] = x_uvs.size(); + + return x_meshUvs; +} + +/** + * Creates a MeshMaterialList table for the mesh. + */ +XFileDataNode *XFileMesh:: +make_x_material_list(XFileNode *x_mesh, const string &suffix) { + XFileDataNode *x_meshMaterials = + x_mesh->add_MeshMaterialList("materials" + suffix); + + // First, build up the list of faces the reference the materials. + XFileDataObject &x_indexes = (*x_meshMaterials)["faceIndexes"]; + + Faces::const_iterator fi; + for (fi = _faces.begin(); fi != _faces.end(); ++fi) { + XFileFace *face = (*fi); + x_indexes.add_int(face->_material_index); + } + + (*x_meshMaterials)["nFaceIndexes"] = x_indexes.size(); + + // Now, build up the list of materials themselves. Each material is a child + // of the MeshMaterialList node, rather than an element of an array. + for (size_t i = 0; i < _materials.size(); i++) { + XFileMaterial *material = _materials[i]; + + material->make_x_material(x_meshMaterials, + suffix + "_" + format_string(i)); + } + + (*x_meshMaterials)["nMaterials"] = (int)_materials.size(); + + return x_meshMaterials; +} + +/** + * Fills the structure based on the raw data from the X file's Mesh object. + */ +bool XFileMesh:: +fill_mesh(XFileDataNode *obj) { + clear(); + + int i, j; + + const XFileDataObject &vertices = (*obj)["vertices"]; + for (i = 0; i < vertices.size(); i++) { + XFileVertex *vertex = new XFileVertex; + vertex->_point = vertices[i].vec3(); + add_vertex(vertex); + } + + const XFileDataObject &faces = (*obj)["faces"]; + for (i = 0; i < faces.size(); i++) { + XFileFace *face = new XFileFace; + + const XFileDataObject &faceIndices = faces[i]["faceVertexIndices"]; + + for (j = 0; j < faceIndices.size(); j++) { + XFileFace::Vertex vertex; + vertex._vertex_index = faceIndices[j].i(); + vertex._normal_index = -1; + + face->_vertices.push_back(vertex); + } + _faces.push_back(face); + } + + // Some properties are stored as children of the mesh. + int num_objects = obj->get_num_objects(); + for (i = 0; i < num_objects; i++) { + if (!fill_mesh_child(obj->get_object(i))) { + return false; + } + } + + return true; +} + +/** + * Fills the structure based on one of the children of the Mesh object. + */ +bool XFileMesh:: +fill_mesh_child(XFileDataNode *obj) { + if (obj->is_standard_object("MeshNormals")) { + if (!fill_normals(obj)) { + return false; + } + + } else if (obj->is_standard_object("MeshVertexColors")) { + if (!fill_colors(obj)) { + return false; + } + + } else if (obj->is_standard_object("MeshTextureCoords")) { + if (!fill_uvs(obj)) { + return false; + } + + } else if (obj->is_standard_object("MeshMaterialList")) { + if (!fill_material_list(obj)) { + return false; + } + + } else if (obj->is_standard_object("XSkinMeshHeader")) { + // Quietly ignore a skin mesh header. + + } else if (obj->is_standard_object("SkinWeights")) { + if (!fill_skin_weights(obj)) { + return false; + } + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring mesh data object of unknown type: " + << obj->get_template_name() << "\n"; + } + } + + return true; +} + +/** + * Fills the structure based on the raw data from the MeshNormals template. + */ +bool XFileMesh:: +fill_normals(XFileDataNode *obj) { + int i, j; + + const XFileDataObject &normals = (*obj)["normals"]; + for (i = 0; i < normals.size(); i++) { + XFileNormal *normal = new XFileNormal; + normal->_normal = normals[i].vec3(); + normal->_has_normal = true; + add_normal(normal); + } + + const XFileDataObject &faceNormals = (*obj)["faceNormals"]; + if (faceNormals.size() != (int)_faces.size()) { + xfile_cat.warning() + << "Incorrect number of faces in MeshNormals within " + << get_name() << "\n"; + } + + int num_normals = min(faceNormals.size(), (int)_faces.size()); + for (i = 0; i < num_normals; i++) { + XFileFace *face = _faces[i]; + + const XFileDataObject &faceIndices = faceNormals[i]["faceVertexIndices"]; + + if (faceIndices.size() != (int)face->_vertices.size()) { + xfile_cat.warning() + << "Incorrect number of vertices for face in MeshNormals within " + << get_name() << "\n"; + } + + int num_vertices = min(faceIndices.size(), (int)face->_vertices.size()); + for (j = 0; j < num_vertices; j++) { + face->_vertices[j]._normal_index = faceIndices[j].i(); + } + } + + return true; +} + +/** + * Fills the structure based on the raw data from the MeshVertexColors + * template. + */ +bool XFileMesh:: +fill_colors(XFileDataNode *obj) { + const XFileDataObject &vertexColors = (*obj)["vertexColors"]; + for (int i = 0; i < vertexColors.size(); i++) { + int vertex_index = vertexColors[i]["index"].i(); + if (vertex_index < 0 || vertex_index >= (int)_vertices.size()) { + xfile_cat.warning() + << "Vertex index out of range in MeshVertexColors within " + << get_name() << "\n"; + continue; + } + + XFileVertex *vertex = _vertices[vertex_index]; + vertex->_color = LCAST(PN_stdfloat, vertexColors[i]["indexColor"].vec4()); + vertex->_has_color = true; + } + + return true; +} + +/** + * Fills the structure based on the raw data from the MeshTextureCoords + * template. + */ +bool XFileMesh:: +fill_uvs(XFileDataNode *obj) { + const XFileDataObject &textureCoords = (*obj)["textureCoords"]; + if (textureCoords.size() != (int)_vertices.size()) { + xfile_cat.warning() + << "Wrong number of vertices in MeshTextureCoords within " + << get_name() << "\n"; + } + + int num_texcoords = min(textureCoords.size(), (int)_vertices.size()); + for (int i = 0; i < num_texcoords; i++) { + XFileVertex *vertex = _vertices[i]; + vertex->_uv = textureCoords[i].vec2(); + vertex->_has_uv = true; + } + + return true; +} + +/** + * Fills the structure based on the raw data from the SkinWeights template. + */ +bool XFileMesh:: +fill_skin_weights(XFileDataNode *obj) { + // Create a new SkinWeightsData record for the table. We'll need this data + // later when we create the vertices. + _skin_weights.push_back(SkinWeightsData()); + SkinWeightsData &data = _skin_weights.back(); + + data._joint_name = (*obj)["transformNodeName"].s(); + + const XFileDataObject &vertexIndices = (*obj)["vertexIndices"]; + const XFileDataObject &weights = (*obj)["weights"]; + + if (weights.size() != vertexIndices.size()) { + xfile_cat.warning() + << "Inconsistent number of vertices in SkinWeights within " << get_name() << "\n"; + } + + // Unpack the weight for each vertex. + size_t num_weights = min(weights.size(), vertexIndices.size()); + for (size_t i = 0; i < num_weights; i++) { + int vindex = vertexIndices[i].i(); + double weight = weights[i].d(); + + if (vindex < 0 || vindex > (int)_vertices.size()) { + xfile_cat.warning() + << "Illegal vertex index " << vindex << " in SkinWeights.\n"; + continue; + } + data._weight_map[vindex] = weight; + } + + // Also retrieve the matrix offset. + data._matrix_offset = (*obj)["matrixOffset"]["matrix"].mat4(); + + return true; +} + +/** + * Fills the structure based on the raw data from the MeshMaterialList + * template. + */ +bool XFileMesh:: +fill_material_list(XFileDataNode *obj) { + const XFileDataObject &faceIndexes = (*obj)["faceIndexes"]; + if (faceIndexes.size() > (int)_faces.size()) { + xfile_cat.warning() + << "Too many faces in MeshMaterialList within " << get_name() << "\n"; + } + + int material_index = -1; + int i = 0; + while (i < faceIndexes.size() && i < (int)_faces.size()) { + XFileFace *face = _faces[i]; + material_index = faceIndexes[i].i(); + face->_material_index = material_index; + i++; + } + + // The rest of the faces get the same material index as the last one in the + // list. + while (i < (int)_faces.size()) { + XFileFace *face = _faces[i]; + face->_material_index = material_index; + i++; + } + + // Now look for children of the MaterialList object. These should all be + // Material objects. + int num_objects = obj->get_num_objects(); + for (i = 0; i < num_objects; i++) { + XFileDataNode *child = obj->get_object(i); + if (child->is_standard_object("Material")) { + XFileMaterial *material = new XFileMaterial; + if (!material->fill_material(child)) { + delete material; + return false; + } + add_material(material); + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring material list object of unknown type: " + << child->get_template_name() << "\n"; + } + } + } + + return true; +} diff --git a/pandatool/src/xfileegg/xFileMesh.h b/pandatool/src/xfileegg/xFileMesh.h new file mode 100644 index 00000000..339ba203 --- /dev/null +++ b/pandatool/src/xfileegg/xFileMesh.h @@ -0,0 +1,126 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileMesh.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEMESH_H +#define XFILEMESH_H + +#include "pandatoolbase.h" +#include "pvector.h" +#include "epvector.h" +#include "pmap.h" +#include "indirectCompareTo.h" +#include "namable.h" +#include "coordinateSystem.h" + +#include "luse.h" + +class XFileNode; +class XFileDataNode; +class XFileMesh; +class XFileVertex; +class XFileNormal; +class XFileMaterial; +class XFileFace; +class XFileToEggConverter; +class XFileDataNode; +class EggGroupNode; +class EggVertex; +class EggPolygon; +class EggPrimitive; +class Datagram; + +/** + * This is a collection of polygons; i.e. a polyset. + */ +class XFileMesh : public Namable { +public: + XFileMesh(CoordinateSystem cs = CS_yup_left); + ~XFileMesh(); + + void clear(); + + void add_polygon(EggPolygon *egg_poly); + int add_vertex(EggVertex *egg_vertex, EggPrimitive *egg_prim); + int add_normal(EggVertex *egg_vertex, EggPrimitive *egg_prim); + int add_material(EggPrimitive *egg_prim); + + int add_vertex(XFileVertex *vertex); + int add_normal(XFileNormal *normal); + int add_material(XFileMaterial *material); + + void set_egg_parent(EggGroupNode *egg_parent); + + bool create_polygons(XFileToEggConverter *converter); + + bool has_normals() const; + bool has_colors() const; + bool has_uvs() const; + bool has_materials() const; + + int get_num_materials() const; + XFileMaterial *get_material(int n) const; + + XFileDataNode *make_x_mesh(XFileNode *x_parent, const std::string &suffix); + XFileDataNode *make_x_normals(XFileNode *x_mesh, const std::string &suffix); + XFileDataNode *make_x_colors(XFileNode *x_mesh, const std::string &suffix); + XFileDataNode *make_x_uvs(XFileNode *x_mesh, const std::string &suffix); + XFileDataNode *make_x_material_list(XFileNode *x_mesh, const std::string &suffix); + + bool fill_mesh(XFileDataNode *obj); + bool fill_mesh_child(XFileDataNode *obj); + bool fill_normals(XFileDataNode *obj); + bool fill_colors(XFileDataNode *obj); + bool fill_uvs(XFileDataNode *obj); + bool fill_skin_weights(XFileDataNode *obj); + bool fill_material_list(XFileDataNode *obj); + +private: + typedef pvector Vertices; + typedef pvector Normals; + typedef pvector Materials; + typedef pvector Faces; + + CoordinateSystem _cs; + + Vertices _vertices; + Normals _normals; + Materials _materials; + Faces _faces; + + typedef pmap WeightMap; + + class SkinWeightsData { + public: + LMatrix4d _matrix_offset; + std::string _joint_name; + WeightMap _weight_map; + }; + typedef epvector SkinWeights; + SkinWeights _skin_weights; + + typedef pmap > UniqueVertices; + typedef pmap > UniqueNormals; + typedef pmap > UniqueMaterials; + UniqueVertices _unique_vertices; + UniqueNormals _unique_normals; + UniqueMaterials _unique_materials; + + bool _has_normals; + bool _has_colors; + bool _has_uvs; + bool _has_materials; + + EggGroupNode *_egg_parent; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileNormal.cxx b/pandatool/src/xfileegg/xFileNormal.cxx new file mode 100644 index 00000000..748cdd8c --- /dev/null +++ b/pandatool/src/xfileegg/xFileNormal.cxx @@ -0,0 +1,63 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileNormal.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileNormal.h" +#include "eggVertex.h" +#include "eggPrimitive.h" +#include "config_xfile.h" + +/** + * + */ +XFileNormal:: +XFileNormal() { + _normal.set(0.0, 0.0, 0.0); + _has_normal = false; +} + +/** + * Sets the structure up from the indicated egg data. + */ +void XFileNormal:: +set_from_egg(EggVertex *egg_vertex, EggPrimitive *egg_prim) { + if (egg_vertex->has_normal() || egg_prim->has_normal()) { + LNormald norm; + if (egg_vertex->has_normal()) { + norm = egg_vertex->get_normal(); + } else { + norm = egg_prim->get_normal(); + } + + if (xfile_one_mesh) { + // If this is going into one big mesh, we must ensure every vertex is in + // world coordinates. + norm = norm * egg_prim->get_vertex_frame(); + } else { + // Otherwise, we ensure the vertex is in local coordinates. + norm = norm * egg_prim->get_vertex_to_node(); + } + + _normal = norm; + _has_normal = true; + } +} + +/** + * + */ +int XFileNormal:: +compare_to(const XFileNormal &other) const { + int ct; + ct = _normal.compare_to(other._normal); + return ct; +} diff --git a/pandatool/src/xfileegg/xFileNormal.h b/pandatool/src/xfileegg/xFileNormal.h new file mode 100644 index 00000000..f8719936 --- /dev/null +++ b/pandatool/src/xfileegg/xFileNormal.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileNormal.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILENORMAL_H +#define XFILENORMAL_H + +#include "pandatoolbase.h" +#include "luse.h" + +class EggVertex; +class EggPrimitive; + +/** + * This represents a single normal associated with an XFileFace. It is + * separate from XFileVertex, because the X syntax supports a different table + * of normals than that of vertices. + */ +class XFileNormal { +public: + XFileNormal(); + void set_from_egg(EggVertex *egg_vertex, EggPrimitive *egg_prim); + int compare_to(const XFileNormal &other) const; + + LNormald _normal; + bool _has_normal; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileToEggConverter.cxx b/pandatool/src/xfileegg/xFileToEggConverter.cxx new file mode 100644 index 00000000..91ba21ba --- /dev/null +++ b/pandatool/src/xfileegg/xFileToEggConverter.cxx @@ -0,0 +1,745 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileToEggConverter.cxx + * @author drose + * @date 2001-06-21 + */ + +#include "xFileToEggConverter.h" +#include "xFileMesh.h" +#include "xFileMaterial.h" +#include "xFileAnimationSet.h" +#include "config_xfile.h" + +#include "eggData.h" +#include "eggGroup.h" +#include "eggTable.h" +#include "eggXfmSAnim.h" +#include "eggGroupUniquifier.h" +#include "datagram.h" +#include "eggMaterialCollection.h" +#include "eggTextureCollection.h" +#include "dcast.h" + +using std::string; + +/** + * + */ +XFileToEggConverter:: +XFileToEggConverter() { + _make_char = false; + _frame_rate = 0.0; + _x_file = new XFile(true); + _dart_node = nullptr; +} + +/** + * + */ +XFileToEggConverter:: +XFileToEggConverter(const XFileToEggConverter ©) : + SomethingToEggConverter(copy), + _make_char(copy._make_char) +{ + _x_file = new XFile(true); + _dart_node = nullptr; +} + +/** + * + */ +XFileToEggConverter:: +~XFileToEggConverter() { + close(); +} + +/** + * Allocates and returns a new copy of the converter. + */ +SomethingToEggConverter *XFileToEggConverter:: +make_copy() { + return new XFileToEggConverter(*this); +} + + +/** + * Returns the English name of the file type this converter supports. + */ +string XFileToEggConverter:: +get_name() const { + return "DirectX"; +} + +/** + * Returns the common extension of the file type this converter supports. + */ +string XFileToEggConverter:: +get_extension() const { + return "x"; +} + +/** + * Returns true if this file type can transparently load compressed files + * (with a .pz extension), false otherwise. + */ +bool XFileToEggConverter:: +supports_compressed() const { + return true; +} + +/** + * Handles the reading of the input file and converting it to egg. Returns + * true if successful, false otherwise. + * + * This is designed to be as generic as possible, generally in support of run- + * time loading. Command-line converters may choose to use convert_flt() + * instead, as it provides more control. + */ +bool XFileToEggConverter:: +convert_file(const Filename &filename) { + close(); + clear_error(); + + if (!_x_file->read(filename)) { + nout << "Unable to open X file: " << filename << "\n"; + return false; + } + + if (_char_name.empty()) { + _char_name = filename.get_basename_wo_extension(); + } + + if (_egg_data->get_coordinate_system() == CS_default) { + _egg_data->set_coordinate_system(CS_yup_left); + } + + if (!get_toplevel()) { + return false; + } + + if (!create_polygons()) { + return false; + } + + if (_make_char) { + // Now make sure that each joint has a unique name. + EggGroupUniquifier uniquifier(false); + uniquifier.uniquify(_dart_node); + } + + if (!create_hierarchy()) { + return false; + } + + if (_keep_model && !_keep_animation) { + strip_nodes(EggTable::get_class_type()); + } + + if (_keep_animation && !_keep_model) { + strip_nodes(EggGroup::get_class_type()); + } + + return !had_error(); +} + +/** + * Finalizes and closes the file previously opened via convert_file(). + */ +void XFileToEggConverter:: +close() { + _x_file->clear(); + + // Clean up all the other stuff. + Meshes::const_iterator mi; + for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) { + delete (*mi); + } + _meshes.clear(); + + AnimationSets::const_iterator asi; + for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) { + delete (*asi); + } + _animation_sets.clear(); + + _joints.clear(); +} + +/** + * Removes all groups of the given type. This is used to implement the -anim + * and -model options. + */ +void XFileToEggConverter:: +strip_nodes(TypeHandle t) { + pvector garbage; + EggGroupNode::iterator i; + for (i=_egg_data->begin(); i!=_egg_data->end(); ++i) { + EggNode *node = (*i); + if (node->is_of_type(t)) { + garbage.push_back(node); + } + } + for (int n=0; n<(int)garbage.size(); n++) { + _egg_data->remove_child(garbage[n]); + } +} + +/** + * Returns the root of the joint hierarchy, if _make_char is true, or NULL + * otherwise. + */ +EggGroup *XFileToEggConverter:: +get_dart_node() const { + return _dart_node; +} + +/** + * Returns an EggTexture pointer whose properties match that of the the given + * EggTexture, except for the tref name. + */ +EggTexture *XFileToEggConverter:: +create_unique_texture(const EggTexture ©) { + return _textures.create_unique_texture(copy, ~EggTexture::E_tref_name); +} + +/** + * Returns an EggMaterial pointer whose properties match that of the the given + * EggMaterial, except for the mref name. + */ +EggMaterial *XFileToEggConverter:: +create_unique_material(const EggMaterial ©) { + return _materials.create_unique_material(copy, ~EggMaterial::E_mref_name); +} + +/** + * This is called by set_animation_frame, for the purposes of building the + * frame data for the animation--it needs to know the original rest frame + * transform. + */ +EggGroup *XFileToEggConverter:: +find_joint(const string &joint_name) { + Joints::iterator ji; + ji = _joints.find(joint_name); + if (ji != _joints.end()) { + EggGroup *joint = (*ji).second; + if (joint == nullptr) { + // An invalid joint detected earlier. + return nullptr; + } + + return joint; + } + + // Joint name is unknown. Issue a warning, then insert NULL into the table + // so we don't get the same warning again with the next polygon. + if (_make_char) { + xfile_cat.warning() + << "Joint name " << joint_name << " in animation data is undefined.\n"; + } + _joints[joint_name] = nullptr; + + return nullptr; +} + +/** + * Pulls off all of the top-level objects in the .x file and converts them, + * and their descendents, to the appropriate egg structures. + */ +bool XFileToEggConverter:: +get_toplevel() { + int num_objects = _x_file->get_num_objects(); + int i; + + _ticks_per_second = 4800; // X File default. + + // First, make a pass through the toplevel objects and see if we have frames + // andor animation. + _any_frames = false; + _any_animation = false; + for (i = 0; i < num_objects; i++) { + XFileDataNode *child = _x_file->get_object(i); + if (child->is_standard_object("Frame")) { + _any_frames = true; + } else if (child->is_standard_object("AnimationSet")) { + _any_animation = true; + } + } + + // If we have animation, assume we want to convert it as a character. + if (_any_animation) { + _make_char = true; + } + + EggGroupNode *egg_parent = _egg_data; + + // If we are converting an animatable model, make an extra node to represent + // the root of the hierarchy. + if (_make_char) { + _dart_node = new EggGroup(_char_name); + egg_parent->add_child(_dart_node); + _dart_node->set_dart_type(EggGroup::DT_default); + egg_parent = _dart_node; + } + + // Now go back through and convert the objects. + for (i = 0; i < num_objects; i++) { + if (!convert_toplevel_object(_x_file->get_object(i), egg_parent)) { + return false; + } + } + + return true; +} + +/** + * Converts the indicated object, encountered outside of any Frames, to the + * appropriate egg structures. + */ +bool XFileToEggConverter:: +convert_toplevel_object(XFileDataNode *obj, EggGroupNode *egg_parent) { + if (obj->is_standard_object("Header")) { + // Quietly ignore headers. + + } else if (obj->is_standard_object("Material")) { + // Quietly ignore toplevel materials. These will presumably be referenced + // below. + + } else if (obj->is_standard_object("Frame")) { + if (!convert_frame(obj, egg_parent)) { + return false; + } + + } else if (obj->is_standard_object("AnimationSet")) { + if (!convert_animation_set(obj)) { + return false; + } + + } else if (obj->is_standard_object("AnimTicksPerSecond")) { + _ticks_per_second = (*obj)[0].i(); + + } else if (obj->is_standard_object("Mesh")) { + // If there are any Frames at all in the file, then assume a Mesh at the + // toplevel is just present to define a reference that will be included + // below--so we ignore it here. On the other hand, if the file has no + // Frames, then a Mesh at the toplevel must be actual geometry, so convert + // it now. + if (!_any_frames) { + if (!convert_mesh(obj, egg_parent)) { + return false; + } + } + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring toplevel object of unknown type: " + << obj->get_template_name() << "\n"; + } + } + + return true; +} + +/** + * Converts the indicated object to the appropriate egg structures. + */ +bool XFileToEggConverter:: +convert_object(XFileDataNode *obj, EggGroupNode *egg_parent) { + if (obj->is_standard_object("Header")) { + // Quietly ignore headers. + + } else if (obj->is_standard_object("Frame")) { + if (!convert_frame(obj, egg_parent)) { + return false; + } + + } else if (obj->is_standard_object("FrameTransformMatrix")) { + if (!convert_transform(obj, egg_parent)) { + return false; + } + + } else if (obj->is_standard_object("Mesh")) { + if (!convert_mesh(obj, egg_parent)) { + return false; + } + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring object of unknown type: " + << obj->get_template_name() << "\n"; + } + } + + return true; +} + +/** + * Converts the indicated frame to the appropriate egg structures. + */ +bool XFileToEggConverter:: +convert_frame(XFileDataNode *obj, EggGroupNode *egg_parent) { + + string name = obj->get_name(); + EggGroup *group = new EggGroup(name); + egg_parent->add_child(group); + + if (_make_char) { + group->set_group_type(EggGroup::GT_joint); + if (name.empty()) { + // Make up a name for this unnamed joint. + group->set_name("unnamed"); + + } else { + bool inserted = _joints.insert(Joints::value_type(name, group)).second; + if (!inserted) { + xfile_cat.warning() + << "Nonunique Frame name " << name + << " encountered; animation will be ambiguous.\n"; + } + } + } + + // Now walk through the children of the frame. + int num_objects = obj->get_num_objects(); + for (int i = 0; i < num_objects; i++) { + if (!convert_object(obj->get_object(i), group)) { + return false; + } + } + + return true; +} + +/** + * Reads a transform matrix, a child of a given frame, and applies it to the + * node. Normally this can only be done if the node in question is an + * EggGroup, which should be the case if the transform was a child of a frame. + */ +bool XFileToEggConverter:: +convert_transform(XFileDataNode *obj, EggGroupNode *egg_parent) { + LMatrix4d mat = (*obj)["frameMatrix"]["matrix"].mat4(); + + if (egg_parent->is_of_type(EggGroup::get_class_type())) { + EggGroup *egg_group = DCAST(EggGroup, egg_parent); + egg_group->set_transform3d(mat); + + } else { + xfile_cat.error() + << "Transform " << obj->get_name() + << " encountered without frame!\n"; + } + + return true; +} + +/** + * Begins an AnimationSet. This is the root of one particular animation + * (table of frames per joint) to be applied to the model within this file. + */ +bool XFileToEggConverter:: +convert_animation_set(XFileDataNode *obj) { + XFileAnimationSet *animation_set = new XFileAnimationSet(); + animation_set->set_name(obj->get_name()); + + _total_tick_deltas = 0; + _num_ticks = 0; + + // Now walk through the children of the set; each one animates a different + // joint. + int num_objects = obj->get_num_objects(); + for (int i = 0; i < num_objects; i++) { + if (!convert_animation_set_object(obj->get_object(i), *animation_set)) { + return false; + } + } + + animation_set->_frame_rate = _frame_rate; + if (_num_ticks != 0 && _frame_rate == 0.0) { + // Compute the frame rate from the timing information. + double delta = (double)_total_tick_deltas / (double)_num_ticks; + if (delta != 0.0) { + animation_set->_frame_rate = (double)_ticks_per_second / delta; + } + } + + _animation_sets.push_back(animation_set); + + return true; +} + +/** + * Converts the indicated object, a child of a AnimationSet. + */ +bool XFileToEggConverter:: +convert_animation_set_object(XFileDataNode *obj, + XFileAnimationSet &animation_set) { + if (obj->is_standard_object("Animation")) { + if (!convert_animation(obj, animation_set)) { + return false; + } + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring animation set object of unknown type: " + << obj->get_template_name() << "\n"; + } + } + + return true; +} + +/** + * Converts the indicated Animation template object. + */ +bool XFileToEggConverter:: +convert_animation(XFileDataNode *obj, XFileAnimationSet &animation_set) { + // Within an Animation template, we expect to find a reference to a frame, + // possibly an AnimationOptions object, and one or more AnimationKey + // objects. + + // First, walk through the list of children, to find the one that is the + // frame reference. We need to know this up front so we know which table we + // should be building up. + string frame_name; + bool got_frame_name = false; + + int num_objects = obj->get_num_objects(); + int i; + for (i = 0; i < num_objects; i++) { + XFileDataNode *child = obj->get_object(i); + if (child->is_reference() && child->is_standard_object("Frame")) { + frame_name = child->get_name(); + got_frame_name = true; + } + } + + if (!got_frame_name) { + xfile_cat.error() + << "Animation " << obj->get_name() + << " includes no reference to a frame.\n"; + return false; + } + + FrameData &table = animation_set.create_frame_data(frame_name); + + // Now go back again and get the actual data. + for (i = 0; i < num_objects; i++) { + if (!convert_animation_object(obj->get_object(i), frame_name, table)) { + return false; + } + } + + return true; +} + +/** + * Converts the indicated object, a child of a Animation. + */ +bool XFileToEggConverter:: +convert_animation_object(XFileDataNode *obj, const string &joint_name, + XFileToEggConverter::FrameData &table) { + if (obj->is_standard_object("AnimationOptions")) { + // Quietly ignore AnimationOptions. + + } else if (obj->is_standard_object("Frame")) { + // Quietly ignore frames, since we already got the frame name. + + } else if (obj->is_standard_object("AnimationKey")) { + if (!convert_animation_key(obj, joint_name, table)) { + return false; + } + + } else { + if (xfile_cat.is_debug()) { + xfile_cat.debug() + << "Ignoring animation object of unknown type: " + << obj->get_template_name() << "\n"; + } + } + + return true; +} + +/** + * Converts the indicated AnimationKey template object. + */ +bool XFileToEggConverter:: +convert_animation_key(XFileDataNode *obj, const string &joint_name, + XFileToEggConverter::FrameData &table) { + int key_type = (*obj)["keyType"].i(); + + const XFileDataObject &keys = (*obj)["keys"]; + + int last_time = 0; + for (int i = 0; i < keys.size(); i++) { + // The time value is problematic, since it allows x files to specify + // keyframes of arbitrary duration. Panda doesn't support this; all + // frames in Panda must be of a constant duration. Thus, we largely + // ignore the time value, but we take the average of all deltas as the + // duration. This will correctly handle .x files with uniform keyframes, + // at least. + + int this_time = keys[i]["time"].i(); + if (i != 0) { + int delta = this_time - last_time; + _total_tick_deltas += delta; + ++_num_ticks; + } + last_time = this_time; + + const XFileDataObject &values = keys[i]["tfkeys"]["values"]; + if (!set_animation_frame(joint_name, table, i, key_type, values)) { + return false; + } + } + + return true; +} + +/** + * Sets a single frame of the animation data. + */ +bool XFileToEggConverter:: +set_animation_frame(const string &joint_name, + XFileToEggConverter::FrameData &table, int frame, + int key_type, const XFileDataObject &values) { + if ((int)table._entries.size() <= frame) { + nassertr((int)table._entries.size() == frame, false); + table._entries.push_back(XFileAnimationSet::FrameEntry()); + } + + XFileAnimationSet::FrameEntry &frame_entry = table._entries[frame]; + + // Now modify the last row in the table. + switch (key_type) { + case 0: + // Key type 0: rotation. This appears to be a quaternion. Hope we get + // the coordinate system right. + if (values.size() != 4) { + xfile_cat.error() + << "Incorrect number of values in animation table: " + << values.size() << " for rotation data.\n"; + return false; + } + frame_entry._rot.invert_from(LQuaterniond(values.vec4())); + table._flags |= XFileAnimationSet::FDF_rot; + break; + + case 1: + if (values.size() != 3) { + xfile_cat.error() + << "Incorrect number of values in animation table: " + << values.size() << " for scale data.\n"; + return false; + } + frame_entry._scale = values.vec3(); + table._flags |= XFileAnimationSet::FDF_scale; + break; + + case 2: + // Key type 2: position + if (values.size() != 3) { + xfile_cat.error() + << "Incorrect number of values in animation table: " + << values.size() << " for position data.\n"; + return false; + } + frame_entry._trans = values.vec3(); + table._flags |= XFileAnimationSet::FDF_trans; + break; + + /* + case 3: + // Key type 3: ???? + break; + */ + + case 4: + // Key type 4: full matrix + if (values.size() != 16) { + xfile_cat.error() + << "Incorrect number of values in animation table: " + << values.size() << " for matrix data.\n"; + return false; + } + frame_entry._mat = values.mat4(); + table._flags |= XFileAnimationSet::FDF_mat; + break; + + default: + xfile_cat.error() + << "Unsupported key type " << key_type << " in animation table.\n"; + return false; + } + + return true; +} + +/** + * Converts the indicated mesh to the appropriate egg structures. + */ +bool XFileToEggConverter:: +convert_mesh(XFileDataNode *obj, EggGroupNode *egg_parent) { + XFileMesh *mesh = new XFileMesh(_egg_data->get_coordinate_system()); + mesh->set_name(obj->get_name()); + mesh->set_egg_parent(egg_parent); + + if (!mesh->fill_mesh(obj)) { + delete mesh; + return false; + } + + _meshes.push_back(mesh); + + return true; +} + +/** + * Creates all the polygons associated with previously-saved meshes. + */ +bool XFileToEggConverter:: +create_polygons() { + bool okflag = true; + + Meshes::const_iterator mi; + for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) { + if (!(*mi)->create_polygons(this)) { + okflag = false; + } + delete (*mi); + } + _meshes.clear(); + + return okflag; +} + +/** + * Creates the animation table hierarchies for the previously-saved animation + * sets. + */ +bool XFileToEggConverter:: +create_hierarchy() { + bool okflag = true; + + AnimationSets::const_iterator asi; + for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) { + if (_make_char) { + if (!(*asi)->create_hierarchy(this)) { + okflag = false; + } + } + delete (*asi); + } + _animation_sets.clear(); + + return okflag; +} diff --git a/pandatool/src/xfileegg/xFileToEggConverter.h b/pandatool/src/xfileegg/xFileToEggConverter.h new file mode 100644 index 00000000..d92f4c23 --- /dev/null +++ b/pandatool/src/xfileegg/xFileToEggConverter.h @@ -0,0 +1,117 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileToEggConverter.h + * @author drose + * @date 2001-06-21 + */ + +#ifndef XFILETOEGGCONVERTER_H +#define XFILETOEGGCONVERTER_H + +#include "pandatoolbase.h" +#include "xFileAnimationSet.h" +#include "xFile.h" +#include "somethingToEggConverter.h" +#include "eggTextureCollection.h" +#include "eggMaterialCollection.h" +#include "pvector.h" +#include "pmap.h" +#include "luse.h" +#include "pointerTo.h" + +class Datagram; +class XFileMesh; +class XFileMaterial; +class EggGroup; +class EggGroupNode; +class EggTexture; +class EggMaterial; +class XFileDataObject; + +/** + * + */ +class XFileToEggConverter : public SomethingToEggConverter { +public: + XFileToEggConverter(); + XFileToEggConverter(const XFileToEggConverter ©); + ~XFileToEggConverter(); + + virtual SomethingToEggConverter *make_copy(); + + virtual std::string get_name() const; + virtual std::string get_extension() const; + virtual bool supports_compressed() const; + + virtual bool convert_file(const Filename &filename); + void close(); + + EggGroup *get_dart_node() const; + + EggTexture *create_unique_texture(const EggTexture ©); + EggMaterial *create_unique_material(const EggMaterial ©); + EggGroup *find_joint(const std::string &joint_name); + void strip_nodes(TypeHandle t); + +public: + bool _make_char; + std::string _char_name; + double _frame_rate; + bool _keep_model; + bool _keep_animation; + +private: + typedef XFileAnimationSet::FrameData FrameData; + + bool get_toplevel(); + bool convert_toplevel_object(XFileDataNode *obj, EggGroupNode *egg_parent); + bool convert_object(XFileDataNode *obj, EggGroupNode *egg_parent); + bool convert_frame(XFileDataNode *obj, EggGroupNode *egg_parent); + bool convert_transform(XFileDataNode *obj, EggGroupNode *egg_parent); + bool convert_animation_set(XFileDataNode *obj); + bool convert_animation_set_object(XFileDataNode *obj, + XFileAnimationSet &animation_set); + bool convert_animation(XFileDataNode *obj, + XFileAnimationSet &animation_set); + bool convert_animation_object(XFileDataNode *obj, + const std::string &joint_name, FrameData &table); + bool convert_animation_key(XFileDataNode *obj, const std::string &joint_name, + FrameData &table); + bool set_animation_frame(const std::string &joint_name, FrameData &table, + int frame, int key_type, + const XFileDataObject &values); + bool convert_mesh(XFileDataNode *obj, EggGroupNode *egg_parent); + + bool create_polygons(); + bool create_hierarchy(); + + PT(XFile) _x_file; + + bool _any_frames; + bool _any_animation; + int _ticks_per_second; + int _total_tick_deltas; + int _num_ticks; + + typedef pvector Meshes; + Meshes _meshes; + + typedef pvector AnimationSets; + AnimationSets _animation_sets; + + typedef pmap Joints; + Joints _joints; + + EggGroup *_dart_node; + + EggTextureCollection _textures; + EggMaterialCollection _materials; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileVertex.cxx b/pandatool/src/xfileegg/xFileVertex.cxx new file mode 100644 index 00000000..6fb3e105 --- /dev/null +++ b/pandatool/src/xfileegg/xFileVertex.cxx @@ -0,0 +1,88 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileVertex.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "xFileVertex.h" +#include "eggVertex.h" +#include "eggPrimitive.h" +#include "config_xfile.h" + +/** + * + */ +XFileVertex:: +XFileVertex() { + _has_color = false; + _has_uv = false; + _point.set(0.0, 0.0, 0.0); + _uv.set(0.0, 0.0); + _color.set(1.0f, 1.0f, 1.0f, 1.0f); +} + +/** + * Sets the structure up from the indicated egg data. + */ +void XFileVertex:: +set_from_egg(EggVertex *egg_vertex, EggPrimitive *egg_prim) { + LVertexd pos = egg_vertex->get_pos3(); + + if (xfile_one_mesh) { + // If this is going into one big mesh, we must ensure every vertex is in + // world coordinates. + pos = pos * egg_prim->get_vertex_frame(); + } else { + // Otherwise, we ensure the vertex is in local coordinates. + pos = pos * egg_prim->get_vertex_to_node(); + } + + _point = pos; + + if (egg_vertex->has_uv()) { + LTexCoordd uv = egg_vertex->get_uv(); + if (egg_prim->has_texture()) { + // Check if there's a texture matrix on the texture. + EggTexture *egg_tex = egg_prim->get_texture(); + if (egg_tex->has_transform2d()) { + uv = uv * egg_tex->get_transform2d(); + } + } + + _uv[0] = uv[0]; + // Windows draws the UV's upside-down. + _uv[1] = 1.0 - uv[1]; + _has_uv = true; + } + + if (egg_vertex->has_color()) { + _color = egg_vertex->get_color(); + _has_color = true; + } else if (egg_prim->has_color()) { + _color = egg_prim->get_color(); + _has_color = true; + } +} + +/** + * + */ +int XFileVertex:: +compare_to(const XFileVertex &other) const { + int ct; + ct = _point.compare_to(other._point); + if (ct == 0) { + ct = _uv.compare_to(other._uv); + } + if (ct == 0) { + ct = _color.compare_to(other._color); + } + return ct; +} diff --git a/pandatool/src/xfileegg/xFileVertex.h b/pandatool/src/xfileegg/xFileVertex.h new file mode 100644 index 00000000..2d88a361 --- /dev/null +++ b/pandatool/src/xfileegg/xFileVertex.h @@ -0,0 +1,39 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileVertex.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEVERTEX_H +#define XFILEVERTEX_H + +#include "pandatoolbase.h" +#include "luse.h" + +class EggVertex; +class EggPrimitive; + +/** + * This represents a single vertex associated with an XFileFace. + */ +class XFileVertex { +public: + XFileVertex(); + void set_from_egg(EggVertex *egg_vertex, EggPrimitive *egg_poly); + int compare_to(const XFileVertex &other) const; + + LVertexd _point; + LTexCoordd _uv; + LColor _color; + bool _has_color; + bool _has_uv; +}; + +#endif diff --git a/pandatool/src/xfileegg/xFileVertexPool.h b/pandatool/src/xfileegg/xFileVertexPool.h new file mode 100644 index 00000000..3c99836c --- /dev/null +++ b/pandatool/src/xfileegg/xFileVertexPool.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileVertexPool.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef XFILEVERTEXPOOL_H +#define XFILEVERTEXPOOL_H + +#include "pandatoolbase.h" + +/** + * This is a collection of unique vertices as extracted out of a Geom or a + * series of Geoms. + */ +class XFileVertexPool { +public: + XFileVertexPool(); + ~XFileVertexPool(); + + int add_vertex(const XFileVertex &vertex); + + int get_num_vertices(); + const LVertex *get_vertices(); + const LNormal *get_normals(); + const LTexCoord *get_uvs(); + const LColor *get_colors(); + + + + void set_normal(const LNormal &normal); + void set_uv(const LTexCoord &uv); + void set_color(const LColor &color); + + bool operator < (const XFileVertexPool &other) const; + +private: + LVertex _point; + LNormal _normal; + LTexCoord _uv; + LColor _color; +}; + +#endif diff --git a/pandatool/src/xfileprogs/CMakeLists.txt b/pandatool/src/xfileprogs/CMakeLists.txt new file mode 100644 index 00000000..9d3acc89 --- /dev/null +++ b/pandatool/src/xfileprogs/CMakeLists.txt @@ -0,0 +1,19 @@ +if(NOT BUILD_TOOLS) + return() +endif() + +add_executable(x-trans xFileTrans.cxx xFileTrans.h) +target_link_libraries(x-trans p3progbase p3xfile) +install(TARGETS x-trans EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(HAVE_EGG) + + add_executable(egg2x eggToX.cxx eggToX.h) + target_link_libraries(egg2x p3xfileegg p3eggbase p3progbase) + install(TARGETS egg2x EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + + add_executable(x2egg xFileToEgg.cxx xFileToEgg.h) + target_link_libraries(x2egg p3xfileegg p3eggbase p3progbase) + install(TARGETS x2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR}) + +endif() diff --git a/pandatool/src/xfileprogs/eggToX.cxx b/pandatool/src/xfileprogs/eggToX.cxx new file mode 100644 index 00000000..a677a381 --- /dev/null +++ b/pandatool/src/xfileprogs/eggToX.cxx @@ -0,0 +1,77 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToX.cxx + * @author drose + * @date 2001-06-19 + */ + +#include "eggToX.h" +#include "config_xfile.h" + +/** + * + */ +EggToX:: +EggToX() : EggToSomething("DirectX", ".x", true, false) { + add_texture_options(); + add_delod_options(0.0); + + set_program_brief("convert an .egg file into a DirectX .x file"); + set_program_description + ("This program reads an Egg file and outputs an equivalent, " + "or nearly equivalent, DirectX-style .x file. Only simple " + "hierarchy and polygon meshes are supported; advanced features " + "like LOD's, decals, and animation or skinning are not supported."); + + add_option + ("m", "", 0, + "Convert all the objects in the egg file as one big mesh, instead of " + "preserving the normal egg hierarchy.", + &EggToX::dispatch_none, &xfile_one_mesh); + + // X files are always y-up-left. + remove_option("cs"); + _got_coordinate_system = true; + _coordinate_system = CS_yup_left; + + // We always have -f on: force complete load. X files don't support + // external references. + remove_option("f"); + _force_complete = true; +} + + +/** + * + */ +void EggToX:: +run() { + if (!do_reader_options()) { + exit(1); + } + + if (!_x.add_tree(_data)) { + nout << "Unable to define egg structure.\n"; + exit(1); + } + + if (!_x.write(get_output_filename())) { + nout << "Unable to write " << get_output_filename() << ".\n"; + exit(1); + } +} + + +int main(int argc, char *argv[]) { + init_libxfile(); + EggToX prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/xfileprogs/eggToX.h b/pandatool/src/xfileprogs/eggToX.h new file mode 100644 index 00000000..9abef225 --- /dev/null +++ b/pandatool/src/xfileprogs/eggToX.h @@ -0,0 +1,44 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file eggToX.h + * @author drose + * @date 2001-06-19 + */ + +#ifndef EGGTOX_H +#define EGGTOX_H + +#include "pandatoolbase.h" +#include "eggToSomething.h" +#include "xFileMaker.h" + +#include "programBase.h" +#include "withOutputFile.h" +#include "filename.h" + +class Node; + +/** + * A program to read in a egg file and write an equivalent, or nearly + * equivalent, DirectX-style "x" file. + */ +class EggToX : public EggToSomething { +public: + EggToX(); + + void run(); + +private: + void convert_scene_graph(Node *root); + + Filename _input_filename; + XFileMaker _x; +}; + +#endif diff --git a/pandatool/src/xfileprogs/xFileToEgg.cxx b/pandatool/src/xfileprogs/xFileToEgg.cxx new file mode 100644 index 00000000..d3734fa8 --- /dev/null +++ b/pandatool/src/xfileprogs/xFileToEgg.cxx @@ -0,0 +1,121 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileToEgg.cxx + * @author drose + * @date 2001-06-21 + */ + +#include "xFileToEgg.h" +#include "xFileToEggConverter.h" +#include "config_xfile.h" + +/** + * + */ +XFileToEgg:: +XFileToEgg() : + SomethingToEgg("DirectX", ".x") +{ + add_path_replace_options(); + add_path_store_options(); + add_units_options(); + add_normals_options(); + add_transform_options(); + + set_program_brief("convert a DirectX .x file to an .egg file"); + set_program_description + ("This program converts DirectX retained-mode (.x) files to egg. " + "Polygon meshes, materials, and textures, as well as skeleton " + "animation and skinning data, are supported. All animations " + "found in the source .x file are written together into the same " + "egg file."); + + add_option + ("a", "name", 0, + "Specify the name of the animated character to generate. This option " + "forces the model to be converted as an animatable character, even " + "if animation channels are not found in the file. Without this " + "option, the model is converted as a static model (which " + "is usually more efficient to load within Panda), unless animation " + "channels are present in the .x file.", + &XFileToEgg::dispatch_string, &_make_char, &_char_name); + + add_option + ("fr", "fps", 0, + "Specify the frame rate of the resulting animation. If this is " + "omitted or 0, the frame rate is inferred from the file itself; but " + "note that the file must contain evenly-spaced keyframes.", + &XFileToEgg::dispatch_double, nullptr, &_frame_rate); + + add_option + ("anim", "", 0, + "Generate animation data only (all geometry will be discarded).", + &XFileToEgg::dispatch_none, &_keep_animation); + + add_option + ("model", "", 0, + "Generate model data only (all animation data will be discarded).", + &XFileToEgg::dispatch_none, &_keep_model); + + redescribe_option + ("ui", + "Specify the units of the input " + _format_name + " file."); + + redescribe_option + ("uo", + "Specify the units of the resulting egg file. If both this and -ui are " + "specified, the vertices in the egg file will be scaled as " + "necessary to make the appropriate units conversion; otherwise, " + "the vertices will be left as they are."); + + redescribe_option + ("cs", + "Specify the coordinate system of the input " + _format_name + + " file. Normally, this is y-up-left."); + + _frame_rate = 0.0; + _coordinate_system = CS_yup_left; +} + +/** + * + */ +void XFileToEgg:: +run() { + _data->set_coordinate_system(_coordinate_system); + + XFileToEggConverter converter; + converter.set_egg_data(_data); + + converter._frame_rate = _frame_rate; + converter._make_char = _make_char; + converter._char_name = _char_name; + converter._keep_model = _keep_model; + converter._keep_animation = _keep_animation; + + // Copy in the path and animation parameters. + apply_parameters(converter); + + if (!converter.convert_file(_input_filename)) { + nout << "Unable to read " << _input_filename << "\n"; + exit(1); + } + + write_egg_file(); + nout << "\n"; +} + + +int main(int argc, char *argv[]) { + init_libxfile(); + XFileToEgg prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/xfileprogs/xFileToEgg.h b/pandatool/src/xfileprogs/xFileToEgg.h new file mode 100644 index 00000000..c95815fe --- /dev/null +++ b/pandatool/src/xfileprogs/xFileToEgg.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileToEgg.h + * @author drose + * @date 2001-06-21 + */ + +#ifndef XFILETOEGG_H +#define XFILETOEGG_H + +#include "pandatoolbase.h" +#include "somethingToEgg.h" +#include "xFileToEggConverter.h" + +#include "dSearchPath.h" + +/** + * A program to read a DirectX "x" file and generate an egg file. + */ +class XFileToEgg : public SomethingToEgg { +public: + XFileToEgg(); + + void run(); + +public: + bool _make_char; + std::string _char_name; + double _frame_rate; + bool _keep_model; + bool _keep_animation; +}; + +#endif diff --git a/pandatool/src/xfileprogs/xFileTrans.cxx b/pandatool/src/xfileprogs/xFileTrans.cxx new file mode 100644 index 00000000..afe58b25 --- /dev/null +++ b/pandatool/src/xfileprogs/xFileTrans.cxx @@ -0,0 +1,96 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileTrans.cxx + * @author drose + * @date 2004-10-03 + */ + +#include "xFileTrans.h" +#include "xFile.h" + +/** + * + */ +XFileTrans:: +XFileTrans() : + WithOutputFile(true, false, true) +{ + // Indicate the extension name we expect the user to supply for output + // files. + _preferred_extension = ".x"; + + set_program_brief("reads and writes DirectX .x files"); + set_program_description + ("This program reads a DirectX retained-mode file (.x) and writes an " + "essentially equivalent .x file. It is primarily useful for " + "debugging the X file parser that is part of the Pandatool library."); + + clear_runlines(); + add_runline("[opts] input.x output.x"); + add_runline("[opts] -o output.x input.x"); + + add_option + ("o", "filename", 0, + "Specify the filename to which the resulting .x file will be written. " + "If this option is omitted, the last parameter name is taken to be the " + "name of the output file.", + &XFileTrans::dispatch_filename, &_got_output_filename, &_output_filename); +} + + +/** + * + */ +void XFileTrans:: +run() { + nout << "Reading " << _input_filename << "\n"; + + XFile file; + if (!file.read(_input_filename)) { + nout << "Unable to read.\n"; + exit(1); + } + + if (!file.write(get_output())) { + nout << "Unable to write.\n"; + exit(1); + } +} + + +/** + * + */ +bool XFileTrans:: +handle_args(ProgramBase::Args &args) { + if (!check_last_arg(args, 1)) { + return false; + } + + if (args.empty()) { + nout << "You must specify the .x file to read on the command line.\n"; + return false; + + } else if (args.size() != 1) { + nout << "You must specify only one .x file to read on the command line.\n"; + return false; + } + + _input_filename = args[0]; + + return true; +} + + +int main(int argc, char *argv[]) { + XFileTrans prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/xfileprogs/xFileTrans.h b/pandatool/src/xfileprogs/xFileTrans.h new file mode 100644 index 00000000..85c77fd5 --- /dev/null +++ b/pandatool/src/xfileprogs/xFileTrans.h @@ -0,0 +1,38 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file xFileTrans.h + * @author drose + * @date 2004-10-03 + */ + +#ifndef XFILETRANS_H +#define XFILETRANS_H + +#include "pandatoolbase.h" + +#include "programBase.h" +#include "withOutputFile.h" + +/** + * A program to read a X file and output an essentially similar X file. This + * is mainly useful to test the X file parser used in Panda. + */ +class XFileTrans : public ProgramBase, public WithOutputFile { +public: + XFileTrans(); + + void run(); + +protected: + virtual bool handle_args(Args &args); + + Filename _input_filename; +}; + +#endif diff --git a/scene/__pycache__/scene_manager.cpython-312.pyc b/scene/__pycache__/scene_manager.cpython-312.pyc index b46907fc83754a2682238d76f88ab42d7b10cdd0..3a452c5731cffc824836cf439237a6b3949b6b8c 100644 GIT binary patch delta 5038 zcmZ`-3w)E+70=D1Nt!-rlO`=GZAzgfrSyTdu25)7r67+|+kz-y`Zf7z18EZOmw=Q< z3xc9U7#yX78;TDUQ65Hrb1EusB6I3+x}Wo#&bP>Zdnj(YhnqX+CT)Rfe&wI@-E;0a z=R2=^!^5YvAHSqc|5bW=ss=wx_U-M7?@G^O_v|X&G|tcb#^C5)=XFk|(Q0OC{H84$ zzxlA1BsH4r^4DtJsmGt+#7u6c>SNJBFy5$b*D@#Ep?uZG%R`9HxUKhI|OO zq~>j4QI^=Q(fCsiCuycx4~=al=;oP-0SkPyNV<|?K2i4a*;j1<)Ouao7K2LGNA!81>jMhsyL8B8s zFcyN{G5Ku~R= zFz=Oufj-&W8}`dVZ$ygra3?5fudsaZq*pQ*MAENiMeuTZS%w?8C|_X!2Q;6LY5;A9 zd(_7c=Ua}d*Bw>Ia^G-F*)}^qG2=N_c-d@+=45TN3%^b~%vUkpX_sSmz3z0q0coEe z?uppg1bDyI0cRqaBdOADNQTxo-v*&kHj5|_O3aEhk(4ZMrIb=qSepa>Gu;gDjWWO| zKd`|hQyLVn$$@eVnnMJwK5Gv&5dcVBc{;TdBp{2nvilxM@7 z&+|NaI(EHZnDN$!FY6xmm;WH z0mUnY0=-gH_9p_#-Efy}5t{_(ZFAFeMk3BDz>`z$uBNS%2`(e3A(%=qjbJ)KK0yQQ z%c+>_A+nKR27+pia>*xmNxqe;Il=>>s8_zh=T7A=l4v3cgW{8dvTBxM(XclXl-8;S z$rlZCJ`1e&qS3QS@M;8w0_+<8@FHlj*G}fdi6zo(uQ2Dnla_VHR()*3vC&V~oXPgQ znb!EurCC2{Z)0O->StOtFB&a`R&#Tf?#29x&1t$*X$GXBQ)iD)%spmF=|uIs6Fw-; z9q%JjfP~Z`{aHl(yx+iO{7QfX1>-!d5WM4l!mfz_XIwL5%izkA_bf#uaxX&PRFKMx z;p);{b~L`CbfcaX!?}qI#!;EOb&0Sv4+$$eq+Bi!7u)M$Y54^9C~PgS)E6TI_DAgT z*UHOvXz<^ajoFQ~je_nrCiOr|RfU5Bs2CBFp-U*63Q$$T^5RcbeWqh&a9_>ACUQYd z>5-HUshdAQ4<<#9wo`WaL44#75j;!`JukN+2Ani|cEb>sdRjoYKW4cx}oP_)=S@c3$vO!#2K}Iw$u# zG?T$8u(-aIEr}1*U$3(Va7ERJq+Xe~6LUL3H~g*P^8N-Qdk9t&+(K|G!OsXpAjRAf z(M%d9b^HYap-8B_gB}kMe2+tE?8f*{J!Luv>1p9w zL_8&oP2pH5Dsy!6A59C`lQ8?r3H?uz{-+6^Avi(sECIcRYT&rVuUdTJU{Ll&F_sE{ zg&3CJuo6XMy}jX(=xJ5&5BQ?0VRZ&QM8`zwZ{$Juki#v-}|nQ5%)m?k(lqw-AUq(G(kFd(Sk_ZDWXK$ZbM2<_r*AuLvV1;bjt-Pgul+Q zCpwi>^^M0)wL0jW-E8PxyHf*SVQcy(*O z!8Vb^(-N*n#R%t%Kw3BtJLSm2{Qk>nzpzRPpV zCI5<;5}i+nX=_*sQ1K*3IZ1j5M4*NHG=q5?g4-8{xm-yhzty@&f{uF;~(c}95E)wz*_z9L1P*b$HtvpB%B9Ao2xQSpZ!DfP+ z2yP}=OAsQk5QGUL1gj9#REkA!S3nL$xk3vTTxgHTzF1J=@X3-)$2MGbCvzimeK&0$ z1k2KVyi`nBTEmLt%a#@}{93#UG)CBaquQDW!Yd0$SwyU09C5j-OA=5(=RCkA%@J7K!4VD!xdl35aFB(m;h#zkSVyT16vJbZS z>rv@Rf0_~R6|b#alJh&<6iYPvr-MKI?8GBeT>LGV9$by*+!MjFeo^D%HZWXgqOsK! z4EF2@Pqexys$O#9`?AIVPFzY8{|Z6mYG!Xhp|gU2P0Jez`U%nq1_(BZ=iweu)7J1n zRGuOAHV5zwYebQu+BGf56>&6(L=V#LT?BU%>_Q9Ac&QM$C545mzuyWw(yew1{%Rttro9_MO{@Xr2 zaogDwM=<5t(6Gr-X}y?x_L)PU-u^6dyK#ei%msrw(I~KWle_;o@)FG(BWk#VRSRAz zhBGYMS-i@PBt?zMw-7vtpr)WG)e@ZvYWf-;ZIdGs&huzT_>uuQGR;HUk8c2g_;g4j-q>0C%8zlMTWhoI^rVMW#Tprv-=9w2j;2oF+HZYn!5`Ss)DjgmebvFgl??Y+44<{N2j^5;$C+fG7lvELT z2pS1yAPkL_F{nQ|7Zkc(B&IA`6%(eDeDUCj&Az#W-4(C9d9QY+Xv-^6LQUavZy0Z0 zLbb3gI&#>^p$jjfqC9kH8a1Lq$E{_oC_dxXDsA>zIQ!t9oBJvX*C(5dNdd1b?}$#8rpplpI_b(7c~%-MQ|Tb#G-BzMfh5MrP3& zQxPwQnjNzJIpinud(r-*J1W>T_`{CTi90ghmX@y~ zSWHk)FqNQ=Uu@K2-6fRFC2wY{scf1M&{ Lu!8d%Lb2t4prO&5 delta 4846 zcmaJ^33Qaz6`nV1CNoJ$CYgjJgh|LYfsloe00|+H1Q9eu5}_K*WHR$78Jx_7_fOa% z0YTd+vfQ}f0wSUY(8Bqx(kdW5qF8OgW3BRcqelyzqo);@V^6J~-uwP6K&AglzPbOq z_uY55ci;OzeOrI@29zj-^RoF1P9Wjf5qxV zcrYp+_-Y3niAoZ`A8Wt6tnnUo(q4@=&kdo7|z&|=jR#Da z^+t?X5)^0J;p3f2=u;d_Su1>l=}k^J8$S*zni5S$zv+-3J|E1`FHa_4qA>>s-Y}X6 zieZ0KG=vfyOv_AiLJ}NID1uuo$Z3v-KXMx|N9hFePdwQ=K7B(}WH_ffoj*2`35|{% zOZ*1wdh7BW3Y7AQV=c?bMtPo#pvDJek2e(P_C_SXr#tMIf;=ClCM{u8U~AGsgM(+l zpOZ@5MKqz9poE~5U?xE&K@~w7!EC5-PPJ7NIftMIdYtLm6N#)vP))MW8J{@ z^aQzb2 zv9fasHNUslDox3YS$bVjUA6AbQX65lwZ6)5+A_Jm!f?96R9~f>N_pF6Zl#4<;i2r5 z950aqJV*^0FCpS{VGj42+IbfT&^1KqLEo(EzD;xKxtu-ISU7TdZ;la zD(ecT7;woKifa?JP$J818P){f6;Dm3Xvr3Y;W%mPWkx^(NFbJuLTY$v zm$r5VuAK;b$`e?ba_& zNVSnMsDG%jjGa^d(wJ^aEFf{a=0Zd?THXw?OK!!@(6S`GZyL=PYnBm3%~Z8_N|9RZ z$PHNMGG9RBqQBMNReOSLFe+9ma|KWXX(qvzBUrNo1mS*Pe7NE z2DgJRrx%e=3sKZ6_`L-E1X~EU609N!64(g338>BUFoGIQvFL6ONTCQ{K?61%XzG!C zy+JR}6`^YC3WNe7NtXG2DBvDWlSunhq~~Ecwk#bF`wy3uu?!{p?hH24WRLn1m77|9 zEcZHFMF;7nxhqMdSxbB!yz5DGiV7UWVcCo@8kGNds#rP9Y}?4Plw)ms80&zg?OA4O zwfuSLYcEQs669o+YVtK;^7ug-I1H!T3(YPfE8$=5E;b*=`*xv{!@eq(Ckn&HkSWqi zwK&9tH8KwBhReMg;-pO03MEn%P7FzQ3to}d1zVE-fHJ+&Bj^dx*a)zy(2Xn)qx|T>x-;DO!KxAi2Oc@lxmTC zeLkuIs$2QGk|hDF>|H4}`J|Av)+v5&^TI$Vj&&-Jhpre|w(_7nKFTJV-d&`+OgXad zIK$Q#+K|T1!oY@USVAW@G_unWw{h#l1WJm7Adw)6z)6ry(4r_C*RiBrq}8Zf8^eK6 zWLgK$BUV1l?JryGCbEEFGQn9|aSBnCt+CU}ZBi{OC7%Zy&GpBL@RNFuC_&to@ANmY zPa$Q~Yo0h%)EkSei<_SL`?K3VJ-+SI@s}|5jYr zu5A-2-R$oBIoT<;vrJS!0qtqkhS!JD+=~q^oZCbK_YrI+I6$Ii6s0Po(?X3|#UqQQ z9xrzKNOO3sEVTQjcC{oq+W&LAK;7(U86Zt`L}+zTif-@<1l7wCGt}x&caq+%D-#%ozqqSg=HEJ}M zy2C3a(uX@bD0#VNbRlV(N+9+u)vLLj_4OhcH{k@;tl2Qu9j@y9HMsE5)%Q+OF$NsP1+ZS;94KJv%0CB+ z^(CVU`CKwi*e!a)sPTM0**7-1aa%ESE1R|%Sh5I)(HHJn!(&JY|EV#-6A$IO%5a99 zgaiG$3vrI^>wdQGqqvFh$4&eoF7sSeCeMPuJtR4gBVRkfu9?4V`&2dq9@w6W33zV% zV74%_17$}p({Ir{3glIGweBcoRj_kMIX1#GJC4%+UArrHp_n_HFM`|1^5LULC$Vby?$ONBJ4o{q1l8W- z4f(y({8N2lE@|7^ObVO?(+ITA2Mv!cV-`5{Siw)krm%Ybeorva*%jd;FzF;JzP3ht zG~WB9NaLDFS2#r1dEu9x7A7CCMya~_92(Hffx_Jjv3af8UCLUN!@I)_Bl;qYSEfIa zV=$Akd?~c;N$tCn$QFdo$9L0neh$AI-h!5Bio6WPdpz{ega`f z8mI3xzM4QR(m|ByZ$BCcT_3VVdb&x_f}q+1A>1Gy?)3+HWgevQ5J8xrhd{(gn09T9 z_zL3wpU{liHDXe(j&qts70l@yn)f37?|0~)y=81b`C#vDtT>ATuAkTJ*7F=BoB2L5 z(9rB=Sfx}qW8lu<0chNp*Oy7dHxn!&m_;DIq$`M;K_I?aP7rmH;4;A#0#UEu6IDqd zigp5-T2F8b!9s!tf<}VH1Q$qnB~j}cE;Xd%WVu$+DMS%C@L%4Iutj&?So5VZ##HfD zRy{MNeAS?zVM_Tj0!7bkP8JhM+?~ezNMjMwyaUcW^K`yY)lY+?>i->8g%{4=pj6Gb aAOrt&@*H@4e|h}3#`aj2@uiMX$p077fRy|I diff --git a/scene/__pycache__/util.cpython-312.pyc b/scene/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fd59c445783d469d522b91c6c06b9f3f9618ce5 GIT binary patch literal 12036 zcmd5?eRLDom7kGjWXX~x*^({!6Y&+0!7|2tI*?#K(|k}`x+e)stD|TXFxZwmBgNQj zQ<4xOTG*I2sZBnLY0{$IBt+R{gOikGA=|(9A1M-RGwbf*w3bcwpSmVJ&0+WK*?ZrN zMk85rdbNx3aQQ3XW64i-#>MDC&23kvSN-a_4(c8KyYu z0L9UqrjtHEle6Z42F}_}Z5MNZp|OqWWV>_+bTp-*_D~#qlHzo4Y9uWON;FiUCe&giK(5dv5U!Pl0IPO%HYbvnW40D7AVX6tnLcYxq}Z1!oE&l z=*b}8g#&!R*UfbX_^=a(QTS(_kNRoV$nkY+;s4HmLNQE*l(r+8pQIAbyvPB{tH=#d zg(Nxp=t_zLIYmnmIgKobV<1NnIjt<0F%K$a%=F-x!hK$$UemlOkCUeQC^x%TEP1q} zn+x^|BKvShclem7+v($bp|%v1<$V3~(RAds^rauA&-~QYR-KYf1s;_J=3_B0FrKzE=y9O~%w^z?~(ueYPSBjok=FD~|}JsFX5&?$TeiWAgi zRl{)bTrgR+BEp<8PSiA9Sap6?v^r6<7OpK5&bkZ6^TyHMgwq4pWfN63!$;2@9epfO z)r9g>`m(sSHMaB0L+?Hm+Zs2w-_UQr19~aH@-g9pg@0l=Z^2I*rVJ|6;ppsZ&l(h` z8K84NiK7? z{qnh&?xl`tAE$b0w=P@@x~4~;Pd|SO%zHX|G5xb2fdx)|{DCwD!VS<~x^?STmvpU& zO33JfmM{({{r4AOhDb7PF6q|QpQKNp%}f{apx?fBX1<SGHrHOap0|P`-UNkM z;q@O5_>X#@1c3Dhj&%qj0q*qxqWM}mZ#N*Eud|~+AlkgX5X`%t5VVC3gZQxk3JP65 zJ|uM(0zTe(igKu@S6*zIO{oHn8!pNZPUQdGy09#BVd zsZIw3v^^Iddck?U_NI!_CUSA!zqCKM;AS*F@hH)$%5+gnqn^-vkw zJGyDKF<#M>GI^xSb(ikFc<*Raykbqt)H181toCusJqgP_qsLN~H4~27afc`2@I;R# z9h>6TO(0OyFkan~sBVd^Ojh3;dGMyCIxE$mbhO2-ZL-vwMD?1OGg;kMRI03e+_WNL zS`pR5!zXODk#BxAWpU0?8hO;sF*+*5D`4>Jp!MuG|9h5&rn|x#_;t^;S8QKHU8`J* z=;li~dzb zO>X{#+JGu{L2-hbDz^<8}5yKlh{sU^!|9`z43}IDbv;o+mg6x$ybwVm}rf)$693*Qo{uP z!e`cPq^=m+YqZxWOM9jEnuW%CB@1Pak>L~|)Z>4lqUNk>;sK5xW%7<(gX3*QD23wa zc`!@jq!m-jDpcu0XJ1U0;_N{enCaIN5OC*3buk&@0J^}nLkOcK&N>}??e;q_rH4+= zTskY6=hUZ{({G%e{^^I)@4XbRfkDq+c=3`;l2c1~Jf6K^S?GmDJNi<|Sqk6{d0!~V z3j|}tYOg=o{Zs%PY$)jE13-a0o(f2w5t=#!zHZ5WeVu?g(9<6Z@PgW6d5pBgvfQpF z(o=S~`PPbf*}u9tw#_F24?+*&6chk%z+ugeDg7z{u&SEzN>8E^9b@IDNa;;u<;b$} znwCUOOROWed}q9VU7~(n?7OM@9TPTJ+~fi| zXWh7CeZsLmb~x#{KVrIRbd2mAuWL=zwZ=sWyh}IVr4NRY__$;*@rp59-dsKjXL+mY!eMJAc#WiWI zj5}Lm%VUms)#jA7ZNgDEWSX!!GC1iZII6V|u@-$$1BqM|) zlr1mY2f#JgTEX%(#PT%KS!H=lcrmEBef6#Bvp=8t__g#qAIe}i_R-8MW6EV1owrMd zLv;fhA9{Y5OMO?;r&ykjUjichQYb{WL-0Kzi25gd0?7&$F+&nyprCBIRtKd66=)B#4sx(30S+ zvLu!`*6Fe5AQzaip|@vVJ~?yx!|BUEOus!0V8-K**&9Y2a=HHI+YjHq^!m)`(?9@a z6H(fbn#1byxhmeyi$nu(C zGz(f&5pR7Q`Uy`!f!0)JaQ@D;2w==nKiqk)bG)iCQPmjTl&o48*)w6O8d>?dWeJ$l zNZIJ2(Z}Oe%_%DcpbbOjTlqLv*pkpaJkv_po)g%fx!tC{W@Fo(xn_iTO?ciMREqJu zkYZ3rP(JTiI2|;oQKeCU>E*&q5vHbaLz3djaJoXtpu&=c(me$v`?PL`gp$+eKf3+w zRXNTadUfXgpNC!WD}m3Qb)|oG0x~f3C4kk1Z6M@wO_LsSC^~c&b1o&4buEE*f?+(+ z14^v-N?8*~I0Dz}>I!yyxeh+y2M*W)`IT~Def}^nAnlXWE6z-hun^$Y!r+>-LF*=b z3?`HW{5&o=Ncx0F2Cz^tGB2I;0_u1c3Yp!lN$FdrY?Z?s&utvHElt>#jy{mIHAVE3 zn7)LI(YCnRlhQYX5hDZKn5f)1rcYM369%Y|K5SZ*s6=?ITtB~QZK85*Y*Vswi>hg1 zYWF`d!eQzVK;F-31iYQpcLj<9Dh2nY2z@|b3nXl=u z5It3YCE9@J`&CYSelX4WH8i!KVkly@$WkB?k{%noed+0$S1zZozBD!Vwq%L!Qc*|J zWrC>l2|^$w+%1#N&GKyn9}t3J-Va8{aK4aFWJyl^F74^pcs%_Ek4KgyRjDtT5iS>o z;{_gk`jd1dhL+D}9XCm)h|G~-M>m-cnOFtQRWxQNfFK$&)1VKcZoua5PzOXg7@Oxu zEZd-O|Ayjze4z<;3Hk~c44k0m7^9;!Vw|m^YL?GYtf4XTjY-+FA3uG3q~nHV<>ca} zmmC)zm+CIoMLWiv$;I1;%#+ew7{2e^eWRWmww6hIb-ZrN*!SXF9*);MlCtlesC9pC zch4F@IRrXex1{B z+Kf>a0%w((DtYQ$ zKUPCEVHW&@@Bon_;k#tqIf5CpmdUKTP2m{^V(}u=>+3wqx1u`M4@vEisD%h$LjQ;0 zu8MDzO4v>2fuYNH%*#Ql{IFOC;Ln5Y%a;rv42EEScJQ#2hRJg{5kUg=5--6ofOsz* z66&|Zz5d4HQzYNXdF)Eq_b(<*_v>+zQ6s-?ss=5o%as06E^3_=Fe@*ChT>$Y_%g{sH!It z7kePNcN69H?mOAq0NZlgzx-2a2lk|hmbNCP7!d~+huF*nrK#?&zpfGf1;0+0 z`88Y#r&s?rl~-T*eHUVdxn6zY{)GT5*Q+nwztHn@y?Pe#4D(XtS+8C?5AawO;28(l zd4N|s$R20=N;z^?0iH=(J0YwTa_iHPD?k&VBGH6?!|~>w0pVyU*wb%>YS16<0?-k{ zXt+Dtnx(Z$ab5@1)qnkIyJIpIJ3Y%0^0b!Sdg4OPFpo%Zinms zmg0yhlZuSK3}H_~H(@2XW&}&L!683vW z|1N27&Al_2w6CAAFOvRdjk+Zjf24Hfw%H1*yynl>kgKylX!mEzbAVgnO^xuva|6}<1;#9!$}%Tfy>~)^hn&00q|Ldjf?Jhd z1-5czOEO@Vb5|cyoJgVY`Av8yc;rrR$mbZiQrv+x2(({A4QMiQYPQH3^D{Jkrlk~3 zJx=wR1&c=sHZRgOkU5$73@}zoGg@<1ap#uC(gi1F025n^Nryq=(H~P(dnVH7zRm8#b=&N z551|*C53Tmc0E4klzK-7EaIdiA3*%_#l;T+j^MSpq&TS=GL%we^`o#Ix~9p_3)rrF>YR; z(r>^R-@G!Vcaus>O23v=R;Bc-No89~-%cu9Q~LWzWp_%y=Sv-BFvqRS-uU*V$1gtq z`rnQ|5;w0+>04)YtfBljrk0!4%U@}TJV^5I&9Tzx04!tPFm3&6Rzn-gZC1{ zZEY=~a+>WGMNnWhK>;LhavA_~l@J|0jggTmLIIMOe-w(C29&uktXA)`Dn#%G7*6w+ z5?{gIAx%NbH!lzRHKQs>BUEt{TjV)xJ~24+ECXZ0wR*&XGh37$NhD=JX0V*-dhwH) ztCy#)o`n+jKzaS+YWEv%st<+`$CKC7g#5o}g}3IU~k79aXoJj_aMbs@)I`86Kb^ zU7$(@xKp;(vl^zn8r|?i=N=k0#CG1WZw8+(x&64iZEW+{s`!fCDf^x;^;CoF!lUOO zy>RgS!Kgd-ShD^;Smrl5#!U?gQ$u|5`dD{-{jT_;-8W2oAR>aNK}ck+jq7W^nk}Qt zt8dz2l?u(}tySACn(G$Lb{h-3XTLx%0~=OhKO-)Jc)en&7k5pCVW*c@G5Boah zmJ+X*3;MlY9ut_n3kzJWA-flNv=g#&$K#?2k0?triTpOKwPS&ViATWUk3jJc6iIVP zFofTP#{ze+2}rz7Qa70;bBvDNLC;xh*}A#561Hlt!T_}@8*86yw6e~*Wkx7!EKn?| zf_rPAxU;4LE>|vr;sM&mmfvZtU~8o(q5hmLw)ore{|*j0X9JVsd!a!-29z8JY&4?7 z%i#9ZC!bEAy^?hw#o-~Z}mWgm*Wtab|;*7y<$1`2YNb6`&>WCrG}!z)aWhJV=ud2ZP7C zrye|hPtt}oOFei`!icN;#!Yc%6B)eQ$OlmvT^Nt%D3;*cf%O(1e+uuzg295A1bL_n zFUcc%u!cUBytBLdEs~|)$A{q_z7}CK6th~IrvISV(99h@MVI~uW%&)|{0+7EPr7{! fUHb=$l!x_{b!F1xK4F^EY3Pl2N;ULmqWb>;+*LPZ literal 0 HcmV?d00001 diff --git a/scene/scene_manager.py b/scene/scene_manager.py index e5072cd7..f3239bb0 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -15,6 +15,8 @@ from panda3d.core import ( from panda3d.egg import EggData, EggVertexPool from direct.actor.Actor import Actor from QPanda3D.Panda3DWorld import get_render_pipeline +from scene import util + class SceneManager: """场景管理器 - 统一管理场景中的所有元素""" @@ -47,6 +49,8 @@ class SceneManager: try: print(f"\n=== 开始导入模型: {filepath} ===") print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}") + + filepath = util.normalize_model_path(filepath) # 总是重新加载模型以确保材质信息完整 # 不使用ModelPool缓存,避免材质信息丢失问题 @@ -59,7 +63,7 @@ class SceneManager: # 设置模型名称 model_name = os.path.basename(filepath) model.setName(model_name) - + # 将模型添加到场景 model.reparentTo(self.world.render) @@ -120,6 +124,7 @@ class SceneManager: """ try: print(f"\n=== 导入动画FBX模型: {filepath} ===") + filepath = util.normalize_model_path(filepath) # 使用动画管理器加载FBX actor = self.animation_manager.load_fbx_with_animations(filepath, scale) diff --git a/scene/util.py b/scene/util.py new file mode 100644 index 00000000..678beba0 --- /dev/null +++ b/scene/util.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +跨平台路径处理工具 +""" + +import os +import platform +from pathlib import Path +from panda3d.core import Filename + + +class CrossPlatformPathHandler: + """跨平台路径处理器""" + + def __init__(self): + self.system = platform.system() + self.is_windows = self.system == "Windows" + self.is_linux = self.system == "Linux" + self.is_mac = self.system == "Darwin" + + print(f"路径处理器初始化 - 系统: {self.system}") + + def normalize_model_path(self, filepath): + """标准化模型文件路径""" + try: + print(f"\n=== 路径标准化处理 ===") + print(f"原始路径: {filepath}") + print(f"当前系统: {self.system}") + + # 步骤1: 检查原始路径是否存在 + if self._check_file_exists(filepath): + return self._panda3d_normalize(filepath) + + # 步骤2: 路径修复尝试 + fixed_path = self._attempt_path_fixes(filepath) + if fixed_path: + return self._panda3d_normalize(fixed_path) + + # 步骤3: 智能搜索 + found_path = self._smart_file_search(filepath) + if found_path: + return self._panda3d_normalize(found_path) + + # 步骤4: 用户友好的错误处理 + self._handle_path_not_found(filepath) + return filepath + + except Exception as e: + print(f"❌ 路径标准化失败: {e}") + return filepath + + def _check_file_exists(self, filepath): + """检查文件是否存在""" + exists = os.path.exists(filepath) + if exists: + print(f"✓ 文件存在: {filepath}") + else: + print(f"⚠️ 文件不存在: {filepath}") + return exists + + def _panda3d_normalize(self, filepath): + """使用Panda3D标准化路径""" + try: + panda_filename = Filename.from_os_specific(filepath) + normalized_path = panda_filename.get_fullpath() + print(f"✓ Panda3D标准化: {normalized_path}") + return normalized_path + except Exception as e: + print(f"⚠️ Panda3D标准化失败: {e}") + return filepath + + def _attempt_path_fixes(self, filepath): + """尝试各种路径修复方法""" + print("🔧 尝试路径修复...") + + # 修复方法1: 处理Windows/Linux路径分隔符 + fixed_path = self._fix_path_separators(filepath) + if fixed_path and self._check_file_exists(fixed_path): + return fixed_path + + # 修复方法2: 处理绝对路径到相对路径的转换 + relative_path = self._convert_to_relative_path(filepath) + if relative_path and self._check_file_exists(relative_path): + return relative_path + + # 修复方法3: 处理路径中的特殊字符 + cleaned_path = self._clean_special_characters(filepath) + if cleaned_path and self._check_file_exists(cleaned_path): + return cleaned_path + + return None + + def _fix_path_separators(self, filepath): + """修复路径分隔符""" + try: + if self.is_windows: + # Windows: 将正斜杠转换为反斜杠 + fixed = filepath.replace('/', '\\') + else: + # Linux/Mac: 将反斜杠转换为正斜杠 + fixed = filepath.replace('\\', '/') + + if fixed != filepath: + print(f" 路径分隔符修复: {fixed}") + return fixed + return None + except Exception as e: + print(f" 路径分隔符修复失败: {e}") + return None + + def _convert_to_relative_path(self, filepath): + """转换绝对路径为相对路径""" + try: + # 如果是绝对路径,尝试转换为相对路径 + if os.path.isabs(filepath): + filename = os.path.basename(filepath) + print(f" 尝试相对路径: {filename}") + return filename + return None + except Exception as e: + print(f" 相对路径转换失败: {e}") + return None + + def _clean_special_characters(self, filepath): + """清理路径中的特殊字符""" + try: + # 处理路径中的特殊字符(如中文路径中的+号) + import urllib.parse + + # URL解码 + decoded = urllib.parse.unquote(filepath) + if decoded != filepath: + print(f" URL解码: {decoded}") + return decoded + + return None + except Exception as e: + print(f" 特殊字符清理失败: {e}") + return None + + def _smart_file_search(self, filepath): + """智能文件搜索""" + print("🔍 开始智能文件搜索...") + + filename = os.path.basename(filepath) + print(f" 搜索文件名: {filename}") + + # 搜索策略1: 在常见目录中搜索 + found_path = self._search_in_common_directories(filename) + if found_path: + return found_path + + # 搜索策略2: 递归搜索当前目录 + found_path = self._recursive_search(filename) + if found_path: + return found_path + + # 搜索策略3: 搜索用户常用目录 + found_path = self._search_user_directories(filename) + if found_path: + return found_path + + return None + + def _search_in_common_directories(self, filename): + """在常见目录中搜索""" + common_dirs = [ + "models", + "assets", + "assets/models", + "resources", + "data", + "scene", + ".", + "..", + "../models", + "../assets" + ] + + for directory in common_dirs: + if os.path.exists(directory): + potential_path = os.path.join(directory, filename) + if os.path.exists(potential_path): + print(f" ✓ 在常见目录中找到: {potential_path}") + return potential_path + + return None + + def _recursive_search(self, filename, max_depth=3): + """递归搜索文件""" + try: + current_depth = 0 + for root, dirs, files in os.walk("."): + # 限制搜索深度 + depth = root.replace(".", "").count(os.sep) + if depth > max_depth: + continue + + if filename in files: + found_path = os.path.join(root, filename) + print(f" ✓ 递归搜索找到: {found_path}") + return found_path + + return None + except Exception as e: + print(f" 递归搜索失败: {e}") + return None + + def _search_user_directories(self, filename): + """搜索用户常用目录""" + try: + user_dirs = [] + + if self.is_windows: + # Windows常用目录 + user_dirs.extend([ + os.path.expanduser("~/Desktop"), + os.path.expanduser("~/Documents"), + "C:/模型", + "D:/模型", + "E:/模型" + ]) + else: + # Linux/Mac常用目录 + user_dirs.extend([ + os.path.expanduser("~/Desktop"), + os.path.expanduser("~/Documents"), + os.path.expanduser("~/models"), + "/tmp", + "/var/tmp" + ]) + + for directory in user_dirs: + if os.path.exists(directory): + potential_path = os.path.join(directory, filename) + if os.path.exists(potential_path): + print(f" ✓ 在用户目录中找到: {potential_path}") + return potential_path + + return None + except Exception as e: + print(f" 用户目录搜索失败: {e}") + return None + + def _handle_path_not_found(self, filepath): + """处理路径未找到的情况""" + print(f"\n❌ 无法找到文件: {filepath}") + print("💡 建议解决方案:") + print("1. 检查文件路径是否正确") + print("2. 确保文件确实存在") + print("3. 尝试使用相对路径而不是绝对路径") + print("4. 检查文件名中是否包含特殊字符") + print("5. 将模型文件复制到项目的 models/ 目录下") + + # 显示当前工作目录 + print(f"当前工作目录: {os.getcwd()}") + + # 显示Panda3D模型搜索路径 + try: + from panda3d.core import getModelPath + model_path = getModelPath() + print(f"Panda3D模型搜索路径: {model_path}") + except: + pass + + def suggest_file_placement(self, filename): + """建议文件放置位置""" + suggestions = [] + + # 创建推荐的目录结构 + recommended_dirs = ["models", "assets/models", "resources"] + + for directory in recommended_dirs: + if not os.path.exists(directory): + try: + os.makedirs(directory, exist_ok=True) + suggestions.append(f"已创建目录: {directory}") + except: + pass + + suggested_path = os.path.join(directory, filename) + suggestions.append(f"建议放置位置: {suggested_path}") + + return suggestions + + +# 全局路径处理器实例 +path_handler = CrossPlatformPathHandler() + + +def normalize_model_path(filepath): + """便捷函数:标准化模型路径""" + return path_handler.normalize_model_path(filepath) + + +def suggest_file_placement(filename): + """便捷函数:建议文件放置位置""" + return path_handler.suggest_file_placement(filename) \ No newline at end of file diff --git a/ui/__pycache__/property_panel.cpython-312.pyc b/ui/__pycache__/property_panel.cpython-312.pyc index 995a943e36fd54a1f9d3c5139af1e53bc95690d8..84e451b72e61d24beb2a97b1d80492ed390a3fd2 100644 GIT binary patch delta 34429 zcmd7534ByV@&G*Db7Uqn$xJen`aMQt`SzUu#+S5#9|Q#W%i#G->-{TbukQ>DCr|H8nYFA^U#zb*6dNjoii3#EBC{wf zf-8;1M*gmw){se-Y6w|nsR1ZhRspnG1b}vn4PdRs z4)8WhEx_9?w*g#jxgFpd%W8mimNfv^uBl6Ph}nu(5+j~bEF*^2Da!k)QZN7l;x~Rb zkXSKa9YYLaiF#;Y90WR56}+8-L2Oos6SMe|dK?K6uc$v#Bw(KBdO};j)D#d^EPM$W zVv07=$nz=6?bb>gw#aN2%;FO5crr-btF0jkqFxt4i{;`eO;qbdT_7bB#QA}xDx*Us z1UcBzR1(Stzd>~3AAy8&iHE0erW^GV`Y6t|_mbD%W ziX!xazxaLdR0&1(Pdb@wGua%(f_@_5VvSDSy2co+3)o7w5;v0&RwsW<1+z`8{-?z^C1S^_;s zBtAUAr=}?fsIU;D5krZJfExzz`XYD0>yLT>R~lt46Aw;1FzwK)r)D-{^(9Gj<9te* zEv$rOR~MnZ4@)aYkRY0qd*zvXtvnC< z6hbZLzYTy>0Yi3?uo}Owk^M>W$iRHa{_DVZGWup0)?&unuao;Z$ek=M8WasB)DE(e z2=TK)8Dy=fNs9{MoSWwuR9T_Dy z=SGM}$2_Udg}kXsAx5-~EgXa&PCr|1S((*lb81)AmX}y7td-U(J4er^fOxaS>nF#~ zCVv&PbEih5FcBI7q{@Yd@x2+rJ_I&#SMCN?48A@t#*T}K;f(nteq(dJLkB|^iO&`k zsLsIakBQ>ae3N)+oDoRy*0^oqhcN#6{&Q&mfsw5U62#U#i)s$O&Sj;;s7XATKTB*J zUp!7i0VAG3z{!uqJD`xp3Zmqc*Hqhl+S!O>8dGz`$nWCCf@HBVKNoO%E&r8aFF_P` z9Hw|20WXwOUsQ#Ley#i zoPPE0bFV&YF3#ZtMpy+wU;6{atFuyRkyB|aE3L2svHGBXjs7J@@%+T}2wu1!lyvTu z1847jEhm+_h%IJJs?Xu0%%?HR%xO*ddKB~OcsI_g zDX-!ai!y9xL9N|hT_wB%@ivp#VGGr|25cdOh(W)FiH&)oR`waJs0KE=(z{tk&I4%5Q_84 zgz_4@?W)cbLo}_+a@R$o zTf^r+N?fQOUvPwIk7I@|1Rdh;h34ch@s+D_UKFPW$4Db_0LVe%JMqNAn1CWknW~UF z`i$teC_!b$yxn5fqD(SHym?WZkF3gCd@eA0HpDyiLQP3U`HGeHlImr*080td_!hss zWhxb(73){VrlVAG>Ut&O>X~;XmsvhT@Iu7|vQ*TSC#e#!`eH0dY_Cv8tr330fGYs5 z8n>8fxp>;0X$fYE6JEgd*Ugk%0rLsrUzm>9wiMo+8cVqV4Z)tVV2l+?TWPvxy|rTV ztuAD5fs{p2H3G#_ua&{D7Go6%c%7B57Wze8Kim4=*=ocIm9wRRO`O3cwKbMfyLAQ< zVq%q5Sh41SToRqXU=zGd>mAt8XCpU5nLma=3Ycshn$SILddINo@6BjmT--VA#zqOh zS{&EsH@^Dy<{df0C-7e0?kCQwoCw2VM^&Uz2PO2?6Hv1DZp1bKL4v5QUYN!u5OL~# z5uG&a2H_g{&G%xtZp`L)51-mGeCm7Y?F$!m4quFzi5IIwwJ13S0fCJx%!eOSii2wg zk!QslYoa7&=4CX`JP^|_5|L#d5qoOlB|cl@TR_Zjn8t+Ihaw(VXEhwaNLco`i0J5* zoipC`vYQX}&b|)ngB1+cx(q>?xLybug>p*hBP+QO`5i#2ipydl6a%~s zz&1KnFe{&3ZAAmXw6K6z4+48;sokkwy&M*uH(1N0B2bd1`nyoV zGE=djK?u?i@WSK7n;LTb%or^v;^0M*9L>4ac0TQl6F+D;3nKf=JBEn&tq;n4<>fOk zKT(uQ1XwJ~VASGR75J#d5+&Zlg&qcL32Vxyyu2uBm=B{cg8d<`QFbymaf|M_Itm7d zRCZ)U4B|BMs$%rsVuI$DTFPr}qo5dJ6sArP|GmB<54ooZW(C+60)D`J1QBzK@mZ3r zzZ%0}NL!Z#3MyU!!+Wgw@rDFw>%|Sb1AC((vW?Rc^PyH-xP-jg)UD3yP-h)dcBx04 zOi00|#B*$Bh_g3khI35v5yL(O?brsaHP$K%vb*@jU8BiqF?{=YKKPu`;?C{SV%_$k#40|!{r$KE$XSM^xF3{SC^dgP zZ-k}3xNmd3LYQEev;2HmvTGlw+5qZV%2NjwcPJ{a(dS$t6tE>d^iMcCTM59s@`v_Ox(G`#BTZp3@WeQqha?xN1|reYaE&fWfnh& z0-$=2qTb(*-M}A<#-V~xf1X0SN9it@$f}C$5$;qDKa0-Ye8J2vL=cX;IBSQJ{TKnW zeP~Bcm7gWRp|S+lL9Pd7duYAZq5Til>Uj#Scwk5T(7?I?pqIRij)nXVf5>TYC>{QY z1C%DO*FpJ#7$U{j>+CV&zjj23!-|b!YLk|Al~AMF5rDCFr8ui8OdK0yVt?r&S}|bu zAn{<6UM$`jo2Yl_Ey1^r1g0A0C7VOdRo+lg!|TCBp@ z1qIQJ4srxFk@XMybY_Sn2wUzhjL7VXfDV72a{a9jb_BEWlVF{>d4yj4X=jMR67Eg! z2)-qi9eth{vI{R0e|hjN>0*AIk-gapeVxTL@`z0u&X`G-NJp?2yMC%O zI*gv`h>?-yG(_)Pq0v>L7=5oecgJ)-K~KFu<@Ye~A#EI0M7 zCB{|DKufH{LvukEpXg?hh z<_Pm32o$`kBf?xL>ObFRFTT1I#hnH^?KL`LF}87?{8(cIF`-27RY``YlD@oWwgB(-Q;B^0UT-D6+BFo~HO{9&uGhX* z4)avmmvI^_z&QO@nITnqJGou9TE19zJ4u~X8w};2ZP|Bb?_Fp2G@U=(a^|_cXB+p| z4u;_K&pdHv=e8X4S=M~E^}aI?wVXTf^0@~#nTx6|*4d@@mFCk&ww-Tb@_DR@LER;K$*nYgYQBIOBjBDy=-w3A57tC*zFQ zK#ywV;>%CQK~m?F$06yVry{SFG(HCAH8z~PUTKuQH@|zPrDLY0-MX^wHrZ6(&q8v7c_yoU2|zGdJwi8_OqmseRzN-HW#fO)J! zd1-}Be4}*?IU)YunxZPe9J9pC!{bxeZ1+@oH=+aI6+&rLpZKru?yDp|j7t^gv>(HgOr_bop&Fs+`x^?Cb zow-|=(xFRf(Y6nt)TNu;qYG}`-&QAA9yd(A9NK!%pLfaS7iN>z2VQ@R%x)iEpkJ0m zyFv?KdPuuR7U-8N=--p{mLTQdhvpkCI^}7d5kd|U#M>ux$SyCwqZ!Q7yw!gVVyz7{bozEvhrZsYlrrysU1kL4- zgIN5D z=~YCqxaF;6v+;U4gg6H~6-Yz)E~)t4TmHCx{8kJs7((ApWhqmLNu2lgFcKt=e>+0l z_jV?Hx4%7^gu!>XIQSi-JiK-go35o%V(B}P5bStoq*jAGb1Q->@z^^zkW4Z1T?^SK zHoW^veijCYL9Yl+_=a4<%{ZJlo#b|jZ#i4HK?0{{;_5PM4L777!3_Jvb??PS@kQ-I z9$!{nSyNqw+gE8AA11cF7aM;7i*12#8*rFwX$$LHX?42-!o~c}v7)Y1Ek?hu30#9> z$P5hGD7&D@6^Fjhl$Uj~pwQM!@83xpCa1~v7eoO(CJ#gyoHQ{{c5DQ`zokr?lqh>E zmA{XQoa`riJJ<~2Zx7WX%+pLV%imE4PKuJh6RL!eccPRSVwU53qGqx}{!V(}q&)dM zIXpH`2_f$)F!o(P&E$ddcMXA)lN06d#$xoliAoF_$fMIVQ*`oohXqdYlfUPuoT6iM zR*|rxkD+Burjc2Qg;vNaNxdAFG@ivSaG~78Q&CjrkXsax68=49LxhD3^=p9v6mobi`WalkY!)Wn`qQVnap0&AC3N*YQX z3btw`Y^pEur~ZMEA4MF0>>BZ&W07oEAr0lc$7U@c{tf=ftG+9;HL^sR9aRK>l4L@7 zz0!_E<&Q<{Q1(s_GaOi#uT!pZ{q>m#=!&?JLU<8+$*HzhmfLJ#@%tKETpI=8{Ijn$ zz@FOIjXTcnzUSPYMo^Jw?r)Li4NjA7B^bG?R+Lo2#Gs-CyhKVX2iM{fAF_FJffeq| zjtyt;J0$FbN_eSfA3S_^*CAiMD^&nVYjLqGO)Fes4UoU>c_~XyEt*=T_g;x0&127< zKm2%(IaMck#}gkjZ$P!upn4PE5DRHUdFB6~G3;dkZsQOiN7~Ygnw6y!N^UEysI^K1 z^i@pA2N4nth7UY^1UaKz7I-jja*gEcI8!$m_N-&XfKI;#-{6;VjQtJDfiXt)nvo}T zsh{gZn^rgbcj?nUO&Zt|)RNsYr)AZnGn>?1hWO77A?=||dN7tp#=rIrMHV^GE4{ZwgbikmgEx8Bt4&)uleLe4`y!Ued zn)iNQ`;E&x2duzS5_u^@4^2Om+a8+VWhmf!1vN?+U!he-INsiYC?K_)o&cg&)nH&B;=YU5T>J z82~2f5}~?w#mXw!e-}K&fL&^jP?IDX?|B@Uo_-frK`|c574xx%eAt@u@j*faUfU0U zojsn{#&tWdO^3BrTJ11>C@=F?CcFy;xpw;9Qqc}Ub?I)}X=b`Ib&F58SE=5ENZ%$V zeiBV;q0*HTc!hi(!7DWJ=2@2urLg&Ro3#YyF4oo8uXwe~kbGf|3M8_Y{9O$1tX5!+ zl1szB&Y?79AAAbD7rhggW#ZSzKg!_&xmB@z*Ke#6B*pN52MfMhl>jRB)y zR?9Sj?SYeyt0#lO7k7fc`t(B@Py)$Qjnc^dOnl|@my?k4P6bL9;WLPIs__-(do?~Z z5+67*i3TZJ|9)a7Y}U38{<4*jRjsE_eoVp;E#V!=DEtMBcG-Lvitqouh?vFjuZqdQ zR@+w^_a5)A(@)7&Cs;aQ6X`eE5xm3EWQskFkEE2F2;1>6sFUU2M3eEY z8^5WRtJYwlqr@xUEl{Om3xmXY-w)%BbvN-L9*or*#l7DTkT!MJviKiKlz8%zO8m$7 zfuvFV_4{g=J2n2?Ddt|%>I{&)NEn4VgT?t5;)|uSu@Do25CG`VQ&Us1ril9r&Ifmd zTEWVB>44IwRY90O3_&;m8>&X17BxuQHi*X$7XrxL;=e9bNO*(^>#=E&2lhn(deOO* z9MT^;(X1((M4KhS4x0O_x;8?2ICw8di)Swx$o*p8Zc76C%l+3E z!B&-#kc5(=i3_h?=bca{aDA+J;(yQ{@29Ct`fI8|>UqfJ+wI_lr~A8Y?Dbl34MJtI^`RfBTE8ehuke z@SlE7>X)V=tYkk43GKgs#h-qiLjD4MD&aar{NlHF8#t@f26}onf0nCbC1!?R;f4;F zKuA6Bj?aPN(zj{Yg?g#0^Jn??w9vPyP@U6I0*)Sm;LqSmTbGO->+kvgo0$7A3D+*@ zhPd8caNyrbSNH9Se}`YAZ~gx$03`q%%$}5kuj_rkg>p9j;=bJ^xL3jVy-4E6_17}j z@SOlUti0m7tz#jCOd#JQB_;H~V2{&2)v#lf#OL-`5BLJ2n3!BqZL?j!ns_-WAir{k z!Bzpsv~|eI0u^z#rTCMVK!vxR(vmzvl39EJ`AW<8Qn}#$jOhbOmWs3CHZ~!UWF+Fe z5PW7}U(G6%)Rfm)E6S^^PJdLr*5%95f97R&Zy=eL{Vfbp9_~gCcjvetkvrfIOyP(7 z)Tn&^%=CH^I+71Hl&wykU6Oq;-?gt4I8t0e`MJUcK6g}9Vdi#rlb*zq7PeMTMn;{+ z>~cuv)No~n#sgsqiY^nIQ%1tnlGqQWxVO!g?k7s7HIP86Qnp1Ih(Z@I64JuvF;*vp zv2>VkkWbhn(c~a|Gm{vTg>eusb%8Y)K9A5sNotsE0(-6utz*mLe!)AIFo3NDIENK)@$TrTDfC zK?@@Zq)^2v6oM2A*GUwLgxI=)Y+n(LVp|hP0B|ZxAj|w!NFc$<{zxa|5^urxTLJvj z2xwh+_@0C2a}Pdywt4gUZ5z&Qcqzv$SlX6mka=YA7)&`9K`w%Rs5pO;FCE!enPj|N zI7--nEHaGP*o|4_^M-!t=2Y0rp*#6F;nZ7=<#V#()W{iB0^a7m6*@LexF+>D**(N| zD&cUz3M+V4R#>eKH9~K)N(8(S%i~1%Qpk913f+atJrr{K1AZma>4d(-!EJE?N$2zf ze|c*q8=g%}h7A~s(>qTwS(AwO!dHl$>vSCYy{`z!>^|IGH)y+c=K2 zBwes5ME`(m2FV#`xw~$OZTR#}1w`gE9oOV-JLUcY?C}xAKo_gnEeq(hHZqb7qrt~< zNOS@4kd;jyP3BD4j>Yn7`x!KDbvy78cHoLi;H-2K z_(0&Jg$o6)3%DBS!@&1rEl34tDqH^miDFU9NM>3eQq*B06#Y&;aBMGyP|WPhNXAqL zrl?1NN}CUHN#?se?ojgzePq5zmS8ius||81Kmb}5}O^z0+%Ix zsH5T*marLRB#xL_MH!hy1N_gETD=UPVKKVy)u{&1U~%2 zutUNcr2g!qO#5^Y*NA;&sqWgcw4atcHRaWUt9iWPD3onFFe~hOl>|x~67djJm(0ba z&m!Si+w0ZjdquD29R+ups6yhDwse&>T*ULpJyCLox| zn%9ux+&(<*X`Q4WTn>iZFBYrtdaVKTIHOp39kBh}Ix>WYtK06Z1JM=CYjSD!zhFfl zu-hDDM9hcy{tsUhr$!trlCx=N8FHDlTuCl&gM+V2W zL#86(8YR9~HHy zl#$O@-1^H^;hXD8)hw*=fM4HfU+>Q1O>tZIc#okCUb3r#sfeoA!y-l-`9H5fq`wKgA2f2S7Rtf&B*QJl}H>}|}03Wr2!@8C2xRVr;TUqCwJKrKJ9rbDOD-V(zC^^K8d&#N*-*Ndg+qsuC zHE;!nBzMYfa5$68=|24CAwBqfS6E90Y0BlEws9^;h2zvp>(LUv`s2j!V`OM4rp1Zf@{N^Xm?AUqcj;$^;$Q!_l zUn6s|**aMEZFrna_Cqsgkzir%kCXZ24VL@_N$GF#Ag%Y_!H(p-&f9kiKe_7vFH{w` za3TiY-An;jaKc(F#aFgBz&BsK>Hjx%7U*6Ex^x<^u@OsOk3bUj@Mc5X8%|Yy9%(5z z5Mt{FNs9vOeozBh?8_I)O>G_fNusisw)^90(hl9#{tSu#ue%FH_J7@7z8&K3kN%Zi zDz^air{C43)xFd^S96kHPs5fb@s@gRRHbcSJWD?4-O$5_$rcxr+{OxDAakJA_r35x zFv;ovrPcr6Gl{f~_kZS)OzgK57>;c8&D6-vG6DIxF$gnMw1IDJ1BUsWkPB_E93ggj zFP<6m3ONtGm-H%0oN^h;fz3&;&Gu&Orcns4Nlu-Go9nJc(riR+AS1;zhJE-d(UL#6 z^}I^z3Hb?eSte&k-Xy`j5IX)QNhGhcrLT}kW;{lo1ms>jMw0*EWauk6!vDwFTlzJ7 zK)V7BiC1@kw68IYW#0@o7;Y+i|1F{;Uoq%_4Q$cd2IaeexI`$~DRlkXX2YU_1i! z(-ZO$Y(+fw!<$ou^N9)@&N0}1ACe(v?vaEl#)h(95^|D!L-`Hz?dT~I8HE0O3(M^* z-CNWHSnx+=ADCSae?%UYlTx;*18fIN*q#oO88-`Sn~i`UX}B5RaFz?$ohlYm;Evk(FvXTw987{zz4w4eqA9RiP+m2f}S$k$+ef%g!8E<%t}jPL;f ze0R}DxVyK|W?}OO^X9YponZ4B-*#&!>49xOmiZNlXj^ied?hEdSpKKvMKXh3`IOuo z$vMM-C#8u>Etf9;v_lIAhi%1!5e%J zzTJg@)9(5-=2UbYDC4c;B-)F)kvM{fs5U?7M;DnS{=^w{-SF808^*w~rs*>PADBUs z-P`u_DKY}+)CP3gMrY`;(;zKeEFtw>Fa}8Nz&8_u5CowBYH_pq%;x6PuihtFTg?JD zbcA7oaHcp*(i$S*y(l$M+H>aQ@S!wf9f^rO#1^9Q^KL*vKo?CR7C{^WGXS>ha%Cei%r{E7;< zG_uvDqLO{%Ti6Nuf}Q)8+~dQxJH8`dz@;y^(Z^O_AjyFB-U}pbBreNR=|8c2$z2Gn z;uunkA?F@=_WXv>vJb9&+}vyU%SsARJ`hULd2CZ$Q!_aZY6+na?UOlH?dJUT~mF z3vbVvUI?ihi!-M%mir@kaeaD`1k(U5`|cu%Zb*Pw&To=?l;j(gBjFJ&Z89R-R~=7> zFfd(PEK|XFEfio1#K$Qw2e&=6&^ZI8`*v#W)`@~pEeL;tXhFm@xM5N#ohzNnwp=2S zs!f<7gtg1zXiTb0Iq}v6%GigO$jDeu5R|Lj`)}z|i5f0j^?m3bkVU?2oAV=CNGWVA zJo!&DS%JGoV*aF1_C_vEOO|}1g|Scu>_K_E3VOHKwFZ%__9rrs#IwCWkthxJP7y4E z2Cz4OA}RR=kQkgl(5@=A|W(H z%NGAk7Dr(XWf0?eP{!c*kJ0R-pGkO(+}EA2UeU|>Pr(|)X%3u>v#8kkD`Y`a1Aol$)q?Tnbgo}dO1NCg0ipHp+qTt&=EwHLAYG&JhA==TB zkE#iA<0w!|NEN1&5TcgQDq;zP!oz)!ysc5DMzFx&NQ{|#v!MN$^HmX7<11X9G#)pP zCjCYdH0Pv{lXXbp0r$}f=-0cBZ_e$+y0}jrU*rv7 z+kPhl{alMZ_Sx?wQc{APbtYqp(sGUY{|oG@>)D`xkpxLbO@PR{fp{Ckpm~yu;Y^(H zB!Z~49fn#OWoOjM7ge%ob2Dik+Co~gJEJn#o_~>7m6!cx0m1Cnf0Hk~(t})PhjQq8 z_PdeJxo-p=B-c5VV*HIJMZLly=Z6iK-a!3TaFj=WOE^EI_EjLz=7(i|_noBEf^Y`L zAMRhla*)zwVr9oE zjaz{{%w@>wBX@gTA#*@E^&{SWPOfjmGMf+&-s3Oh+W-vPfM6p66bzs>q*Vu7DyKQ| z`!E@wm*Plqi2`Ya;oz25!(j~YGpc4|mw(OvA*bv8dd!3N>-Onbor2yP5?2^1I~J-b z90|^TfrUfl$A%~iM{QGQlxb9B~n> zI~Em)~4v1jP9R-n&p6KH$}BWDm?F zkp*^dtg^4`tWTT@PLonRqGVA&sOaTh>(Bo1r!g7)SgCIW_|f{;tJbO3_)C9%{Ajf- z*^f?dz^2z?^&TR6T~~!^(7D2N0FuGRoMAq{|IYLKw|df=b74;yi9;zQ!oeYVx_T10 zeO!xRy1{3ud;p=^koI&eo=tLT)Xl&eQ9(;wf&Ag&1$cJ`RM(W2mD|_&dbswso?Ja) zo5w2DG-L2eWDGp9f&SqJO*Z(h_QVe8j?LQD?}xWnGjUIMrwuF6 zQ@3Gwhhcb^VdQCa;>~RzH@iJ|PJ8IwV%Z$__xKK7Z!(>CK(CSE zKk#+O+c@|Gv`+^O9>vD)A(8%|*JTxkdL>Bn{L#^@Ih`mM$m$jA6x`7r{`w5iK*`Y^ zRuAZ4&V4NAPl9=gw7~S-*AVQV~A$%dlXW3rUCaMUD z_aqs9BMf>rn2~U14yJLYKOxhF0*^GxPN;|WsApe*`Nhm&IwGXU6wz%;?J%Xb4;tG& zsIX&DVV7wFd&5dXQe%3;Gf!q@_hby|$sE#yFWDEhs))!7fwI{63n8+QoXaw8NazJK zdpnp;)Md#`5f_v)L-du)BUq@Bj)>e^IH+(0oCwiONRuDSOPDY~{?-5`yq8gS;TvLV zt25F8G#pviH6=+JFW8VUD9qe*^4~*foN+!hr_XepWrosR@*b-Q1;3>Pwmp=_Ou&+uO#wX!MW)2h-lxbyHgE24c4j^;AF<7 zjU<@a{zy87!2Jc0bOiZ;r9{zhy%U|bjA*)mlEG~oW2u3-PcfR<#cUXM2ji$Vg3rDB zTj8*#JHaIQT^t>_RH1_&H$g94X_W0A)*UshBWl>8MMrMyjGELaUCYC#Dw0Fx*N<$1 z!yY-Q`v0)fQM*<;HlcvknrT+HXR_i9c3qK!_Eg_1&U{rBto+z<$R_!wUo~%Gz%{U_ zXfjcy1syQnjw__kr{q)l}Yzyb-)tumgonSpFRdc-P#C zZ@e3};M-A10+xZkUCXit(`m4oTQ`{QHCz=rFq<~*8u-+X_SI!gkSfl{}me1XLA3}upAn?TX>fCvCx&6#8v0jR=0k-3iw#kG#rm8b_l*nXDd1;|KInCTPAF-bUc7 zE@rUT$I%HYGlVeF$)`6-%Ajf_xNHn5 zpowa;cdz(aWZYW-^l~s`wbN*-;SP+u6TxN#TiCPHXbN}){|zFu-^QTl5ab~kk02kx zSO9Jx5S%aZ+XMdo&>bv$CJ^e-bgE`Erqf`Rulo_<>Rr0s|4 z)Iu7jVS&#BNR1Qv^C{xu!0SN8r%Hc8A+#McMk3%FIouJA@78czZGSFQ{rM#~aL?0w zu$UJSv?1VT);{ww*Oi%Euv~KokGp1;VG+11B$<3)!M9QbyejlAa|Xac_Rf9DAsU4khomwHMKUD}gM3olobOMJ(%H0KLW=a>Q&1l@Iucf2w=AM7RG(n>YSz7oT63>Y1x|oIy<3ME z@vXm?*_y>PeiAo3;P!@hpVMiyNIPsUzh23Wp6``4U_!p0LP-Jq!5{E0J-L|9CN*u@ z#WX=quCVz_Xvs7$u{o8#fxLViO~Ug@cF9A7^NeRwo$5M$#qn8=b#daXVE~D z7yDuk?l7jqcErs$(;ostJTg$ggX{v{^C*1AmQ~Q3z?AcP1${lJ2ek|QFKd)&w}@p| z(>(GU`)ef^F#o8eC86)(c)mu;KUi}WPx(sK^;5>k!LIBUf-Uy$to)9w{5J-6W=(HI zA$WH+ExlevF@8ST51G2N3p=t4-V2jEm zjxDUG4T_5p-v$>`XA!1ppyk>xF}qY6t8buTG1n{VBzvv_iWofgETB96)3=4Pz0L&y_(9*y+&8d5~yxbn7dU{Tv>{AA7=1y~xN z()77??0m3|+vtQuFQ1_D=5hpqM9=r3jJgCk ztQD@c!wu!L1uIlDt1aLz8m?6Rj1^pAdH2y2rI1|rz@ zCi-N<@0hYbtZ{pWV_hoUeJ;oqkdhO^i>jpFodK>iF5}VxY`+)zKAvINZoW%~p5)?~ zMS98e;p8ru8-27BOxPQ7NS|XDchS(cA9vD8a;Z7LU~`80`845^kbbdeOCjepJg$2U zbOTE(;I^4sIEgDAioZU-m)RS;Xc~E#eY=ZZCVp+F7`><%a8R{5&cMmM?r?PW<=tNZ;_DV_HkiD?%pX6l`eyWHQAOJ81-)7fdyv?bz zl&*pEd3HN&G}-Wqq16D&tN5ud{_6^Q_QJz7lf2AMKTLB-5Q}T3u>oGx`-*$OFt&|T? zVr4i7NtMG*;MJ9trBxPba;d@NLN+x!%WhNJAYr)VLV-RaYG>=4Z4RKPig zDyPCy3#oYLBYy5>WHOeWj=%_Mg$!2nG>u3e1OZO6T+;1mqVx<%*N9Al1hBIg$=-V! zmOB}3pFd3p6Y6rr9WV&i`2kx_Uy>HWO)rjkjA?#8PT%F-J3J`86 zqgn!ie+pr5Sf~Q-#RTTU2QEO=h(V@k1-^K%z-*$zM|RY0{PFH9)GJtgEDaM4Lju{@ zrF0-Od{49qc3h(J=PB1;xbza%B+|20=G{j!?K(p=Y~)g!uCge31YC)TvuAu@(*lJZ zf7}JZT^=}X;*Uk;ZXpzp%QF5zDQ4WJf+L^+;fF?y;afCY@WwZ}#cz|^odx!@lz4@m zNx?HfwRjVwCeWvNNXxX#Xlj2YY5FNith9!*AK*I2B%eb3-4z0ksFmF~4!RbO+I~t# z96h&1YJ7|L7=)pC&n5t*=GRGj5vN-;V*C==P4c!m-F^F{Ce)tgM$FUADu;@#!rwM& z%Ah(ndMO>01ebHFp7N4rXx}`5K_q*<4D8~kZh<{ti{9dUt)}djVSMxOs0P^Y{T`yj zHc3H23&4VY*DO7eKG)-Yqv?8+Kafqf9Cm&;sae)iI!t9T@+?5Acn=zDWIk+Za`!cC zLN7IuP}mdZIU6jY4nHsI+-mN*OfCs;h*8_~-OaItIn@2!5TjnF=7w|l>ti?%cij*( z2Zm2Aa`>5TE$8n2^Vxg$pV{(a-8g>#e@>!eM8Svx@w-Rk#J2{S;-zcVc)Bx9cc0sT z=!aJxko+(H^bGunLYYjAuZ|#cF}o&Wj?DYpH1l8y2Ni(TJ+Pz(e#FP~iy|;T!)35j zTP>8YD6isf0^G#G=UI)Ol|7`X8_lhNYWMGvusLwRVQ?7?Z1;WaM-<(orgw~*{@%p) zxl1}n-Q4K1X{sRi*#|b9S^tnPe&hPZPYTAS5_pfCmm*nf-63P8qv1^W?G_vo1& zqi4QnZJ%G#IeMutGJwh1XCCoICbwT?0>L}7y5|3cOVq7^i;xS*3klGCPNOrgJ$de) zga1vn71jU8YE!CvR}1o8CNWfwWJxT?iGYRRefUFNED?7Na*z-T8|;@)xx)zPY_*X=i?^yW7!i>@4TeuNxnR+lX?cYZ9M%%ym;D zYnHgb&g|m*QT!1z9u04hVvy4#v6QwypV{V0{q>$l&hB~Ly?L7&FQAlk`b+yiRrdMS z(v0^G2o(lGlbni`=RaKL45+Fu!Q2qN%vvGMm!nWZ@cYM?E_F>W#!J)vyJY-w_XgO3!0z<^M$vR+ ziNhdhf5i)!hHHj>Zi?7FP@J`8VvjDUTNmA-i{3r|;o|#?yJIptVlumQSr=5`U9w}w z))~8lc4zON*QJX;sSDaMZR@n{GkPLoyCc#%BGNh|GPX?nyFLQ1>s!}p9?_)lF~oHn zG8#Gz8SR->U54r&-1AN9FeG&w(mD)jEwkXlrqd~dA6?s|=rSbv#AZO2mRq_EqkY0K zM|*ni5qo#uypFtiU55FWRkHZ>M+2GyPA6uztnN%4g~gb87ITNe+-(@xVHntLXc*jK z7<@eY<}O1?k0GYpkkVmDX;E|!9Mv&!6jX6KDeI8BGifwtj^~-Z`BoFOO9*)gR;3*P13<)j*6dsbpwOUt&b{Piygdrw}j9rG|K4DoMhOBPG$PUBE zu2H33hGjg;m$FCZcjwOP$enfEFuUQjc|d#0m`?Lp7s3)Hi@FT6`evJcB;dGV(v{Ek z;g@6t*b=Ya%4}KHQq~?kq+2(%LpSud3v!6~)fNj(f5(g=GqYtMjv587N( zz@h;8#{o)sM=K-a$ocHeuaM1? z`NSyfTJZ^`S+zn27spFi$PcTR`y^2En;bn;bM&g3CxcCV6juC!=}08Zs%yXo*{}jE z4&YYfhr9OkTz`EgAL`n0$a414#Hn^qUu2iSKS8db8v3O1kxHQ1u7Dzpq}NcCAV#V1O}rv5N2Z>E-Cw1_u-BP zI0J&$%(z9Dt!@B%??yLxLpGuyV^9otEYXCX&<#FqjM;5%UeIMs>j{tU4o~k0Pj6Y! z89ua0`Ds`*$hr2&!Cj{8o~W3nshqvipPqjx`KcRQX0`|Cbm_+QL_}?wcCwd9JB?g8 zuqCoZ*B(5)OE+Rpk1+Teg|>ch}qZ8*Ew zke#0)J3I=&(F{$2L4I_EzCbH~Q%T_cO|7!Pz)syw!-zj}XoLc}3*Ey)!AVbn#{}W3 zMbGu1^-8fLBn*7X)+^x_`qytT@kI%X_#4qSz%K;2C&{q9LN&-AizhR#i{{DJ$blz4 zODaA^R{5k>;%^G{asHBX=Pz)A!M{FSs;V6ZRh&6=CmyhXL(cY>+F`MGus~KzGA(LbMqoN3*3NQ!}*6V zw`aj;i7*KRxeblaD7@kFN8&EAZoAyWU zi|mdc(h)zT%PR03;$KRj4(eYKVEh;UQ z-HCZDzL184!9dec4~tBJI#j@J@S}OsN~$hk(4ed|?$A27y4udCc_qthD=Ihz0;DAl zYzpBgkQfQ;RN#fjPQP;7M1Cjqk7r0~1ZPr*<`*Ug8yB<@e_dfa5IuMx+X4a4?_lYY1)d4SEW>0Cxc! zWuK{odO{*u@y_CAeP_tvE&e_Fh;Ds+hd#bbpV$*S@ZswFt2<+}w#>q7DjS;Db?Ha+ zL=EbW8rcywvNLLQlloI*RP&VmMf-}nl7@XA6}x-&gSjAkdW_+0?9Q=Jrg88V^`}~+ ze~&S0%dC@nBW!noZ%%|CUMB=LB5}QP5~1R$lxRtersT!ef*HXl`vif#mGL$J9Cb4wSk6NL}4k*0xs&YHmb)2c4 z3S4jWwI74*nSh0I;UldCss&if!;efkHGD4bIS(z_Q?C*~IZ$(5B4@i^#(|fS@4BIE zls=J~1W7`Gf`XT(qQ{1{C?6fuoY5XUxJ#Ga6PC~&mf8`P+8LI%WeUvl`CLA{OP7G} z(mY-lCQbdjbg`#_J?-r<^VetfM4P*#2X{md?v5V2pd)&0XY{xx4GOKm<~7Z?wuhn& z^GxHK7qq}7tRrEy5ERP%clz%hecX_IAwXsreC2X1mt=Amo<91L#jhrju{ht^P>@qF zPWI+FP2o`aF~5YuO!=`)CA_=rq4-OhAAKAnF9LpDZ#1#dD)3R`+zt$`6o(YR&v)s< zrOT@$;rD^Rxm(-da-`Hj>HOjPPHxw_zb)`1Kv(HInBlFHbCVGK^;5&X6G4O8m4E->MOKR8+4!A&^TXRRk|w6-jz14?bOb zsJ9iS2RPwJNdjG_j+RFm|=HBicN z!$4F%$yeasYg9f23dAW882+1W?ZIi?y7bfzT{@qMXZ-pjCtQ9+evIsBj3$4a{OEwd z{L%8Gqm}Uf{~yBf{_agT$;`y*CcXO5?eX!w>BdRMg+Xt+2{k~IJ{N1`BbgFmmn|&X z*F5zV5J^}Cae@HA)iVirytI#TADoxn22H`?W%VGodLu~S#(XfLf+uqfFs?AuXHNjG z`SxTR&*Im?A=~C(3D@2@JlJ23L&os|x(2W6zMA{lX)0 zg-VQfuQ7t9$(=N$3L(ZNTYDMxzDL>m7>qtNkoaqc;)t@hM`v{DGkt{}A5uO7(k&l9 zHAX)c))LZkTUY!D@HS@Cc22u@Mvp13+mzH{O6oEVa0TvO(`8D|*=C|bvaDTYnf(p<2Caa|H-4JozI<~X zKB@1D&*fg}$sMNTW^2pTF4G8!EeW>s&BHlc zWE~xOmchH&KgF%J~Q2^Yoot}}` z+Z_bQuSORA z!!o+mnOCReKfDN~;BQ}|*J`T6sV?DH5|orU{Y&t+{#v*vxWuVrna|SDhInkoUQ3UTI_#1*&1YaQd48bV`rxBb*fHq_S zZ;}zx5M(02yE%km2>ie-Bputv+X(oT2Lc{@7g90eUfwY7_IV#R3=g>qcmR^0v=!#z z8$ZW|=Q`kA0ssrXF?_4XH~gs%VK)Nwd=~a1XhE)6KAsXg)Gf`$^h9OCrD=rS_&nV@<$+Lkl;qCC^Z%?`1|E~nm|%nbSq5{L38C* zzHY3jmBus}G0JOt;YU4i4Je({z=@!A$cQ(19rgvU#|#m_s>e#hZ=A3JB7la{hzD~n yAtz*UCuC9Z-_Hs{s>shWFTBX#&tPmuu;1@@cV>2Gc6N7mzTYf+ zRrl>>AHRS2`uZr~cY5C1xksK|=NC@au1mRRC{dUdW_785nPI9ylr^ORWr0%zMcJz~ zs4RGDa9POIkh0LJp+rFy!xU!k2Nh=RE{ez$im73Xd{TAm&UPW*OigBX*+Cq1j5U=w zwB;32nW?m-#*(UZs3uy9dkWagp|wh;@~X;;DhvCFgpzFymq-Vqt2B3ikWB4iPJd9* z5T>jonne|<^Buu#e@-y_mufQ!V@uSth=0Qc^#-bSc#WATXVN6uG*W_% z=gg!by!%n&%SXOWoF#oN4PmSOBlz{bG>B#U>ew`0DF4b#gLw7@;-j&7NdY!59*{?a z;i(S`H*5P)Z*}(YpOC?t&-H61qY1ri!7?H zG@GoJam7+erIk$z=%B%lNZr}8fH5SJeG)KOlMXkkDjQ+~qln>az2S_$bCbUF_93VB zJ+5n5M$m9q=RR)O64YPMW**h)IztojGsp4~bE%PMcP4r^D83(4ZV6(m;`asR!($0~ ztx^JrudfI3jxydu=CDo1G!o7JVXTiYgePDVsw#_hiZr%GUAU0OGtyy*t_+J45Ja=r z6IwKy+HRvNk4C!^w|4NLl)r;Y3h?}!~E_RZfl zx*o}Ylr%QZ15=@7ZWD#>3B+bW5Y5&mw`eoAy*5vKv>DUk3sT6srE~~vn*e#1y_}K_ zLi{e}oixFaN}7-DB>)Z;jDX3K6|dWAzJqm4&4K3krM}a(b#rL}HWcz}r}aK)oy<;j ziUtf9J6T|$jqKc&EMSW|M}-Ir&1l9TmEi=(@0kNf{yuh_0>#+x>avB7`rZ0v4iQk zz}2xS5e)%7c4gQ%t(Kv>~sObMy;8J zJYB^Bj6 zl(Lb%fOQ0o=3#($#jEqZW&RyXvbb6y`4f;Nv+N=M?A2Tx-&b<)icIj?9x|#X?0QZTV9Sg}EmUg( zTsZW|jg+Izp>-e6NfWW zsZ+`9*wESxF)Ea9F*y9HETxuW)NyV_X@vx}-I~Sepru4Z+3UkPbQ2_e;o+5KYc6fr zd-d5(S5_U!C`b*Fdc#dgg8+wt?AJ4xAwP6lJzi%bupw{@-WxA#5iCYfhd>As2bQ!P z0ZPf?Ra{z9JkMfI4VDL$V6zWC-oTdTcN_L1R??PM%qb~0l@={@7i*-|SZfVF*PDv1 z)uz(PqfE0brLL=~;!1iLYrVm4=f@E{3m+ciC4A=~1siiNg2k6=*tSdqdA*@@_(dg{ z!M@)k8s?2Qs7Nnn8?y(zTIaEy!`wTA5-AH=pK1j6{;Hx}K@ z$+0_(s9lE-_%?YetGZKud|ao%qsU!7R#lc-%2R!%2zF{*j^-TRcCg6tleB`}9Jprd z#;22rhU4R(BTh!^C%!@o8?ov~2tEK%Ee6a*`vI2)Sj_j#x(_U&Ak{~5U@b8f3;aTD zf=#V#E#L>F%WUQ37~c_4GgT#Ph5^+@)^Y;`-8e6gOlS6zBn{f7 zG?ASzDU7&|#hU=`7}Hq2m_2r%v-%XMK0Hp^hxPB;D7l0{m7lPlpfe3h4z0OF0-2#a zX5teQVq>S~#Ra!ZTVf+iowR3*+J{TMB7Vch?KLqhM9P0)6+yWKniWJz(%l;^ZrP}m zT`MaDGtDlKjzRHCw-C4u);-QqKo7FR<*|WCtr}g)m1T#nu6Qg%I?28(9|=e@Dk6h4 zfWNg%ho-vKNY@KNH2ZtSB&h9I8P;BHvX0eO#wG|t!lAsh{OXEFue|j5#n)cU5Ux-v zM?NdSALjSp0YzAF2RN60qtJ-P!DzR*CMCC#w3?K#Pg~it`9|4Bsu5kx4~W?hIfbo2 zTYWLShz|oqU$E{%(M*`i-I~?C+_G5>v>h)^M+`F%6agqm4Rh#W%oi@Q&aEgPZ<-_7 z@lh*hxB%}7k64eTwiY2cHXRzklwT=1G!|I*&tVT&`4syg4{jG~!}d6Yr6mZK0!R&& zhb;=tp)0PIBulxq2q#qGFNHf4PP6&h+)7DWhs_^Hz}W4o`$8VUA}%eZ#}KSVaEmRl z#%Y8<%3_OGkF2}Zu=lXlJV1Ky#5z9$NY%)_4!|42=ZmQrd+pG`+8Ndysa`UZ zw^$cjv@skyA^&@!E?D0u;OL-so8Xe*bg*`qdZ;>);g6A<@ z1xn{?tqk2YBy-V0~LW)JpwG72N$0jG&$Ngh>+h$sm` zG6I1-f_c?v$jQdxM+4zy1=yy$|(Z*-Hc1wk6sm*ncX(u-#}8nLOffkA~FL ztoxZLF|j~@qlTdJ7p23jeai=))+@e_OI zF7FXC9J)~DlttGbD2q-)76aKk%i~&eaBKO{kkK9u5>l{1oKtx9EBcD*UDj|OFy`(c z`)I|ekTD)jlLf5=DY~Ki%5`K`v%wdD#fvf7hQ*7G4m=n7Ze>9+Kn@}dVE0w%?y7Kw zC0o912mU-hlr|fJM)<@D?4H%@Rp?pyjV2n(XVy_ogX-Z)B<&>JlRiXAks=AW#tHl^ z-V-t(jh-Mp#ZE#s$3D zw#U4Z)wQY;MXkC-u}V>dJr+N4g(F}5tN|>pAjHpm`gw(78Zc3-sr6dLnkI!Z_1a)I zxG;#tPuGso+O!)LW{ph+&}*}**2fy+LSXi`Y2dD0!)LR47xW%~L{VN~MSm%NHZQZz z#i$ey@r!knNVG~aI7=DjUS_>bWA;T)zfrN7*6M7!zo6(RYWT3<)*5^I*1+6XtSqJj zp}oxqTKe16HXpPEz^1I#1Abq;BR{XzTcfzCBMDOb+PqhRdVeM%tahCbn{^^Mz!&RS zQU2m*)3a55jQqn~sso94^0oQ7d$@{2JSr}!*5BrD4w%;$44|J!Lw{GrT7%8N#&itk zU(S`)<`-l$xTHZ8tGLMmF`uK4`y1(!I1kAN+X7G)E;K}8HL~lEhwy)nAwg_oj6VsK z!$3!3d|3($p|${?zK_OITOgmm4Bd$_SXjSedCA^+-ylCQYPXbWef5o^7wiWhJ%}N zvcBs>*z6~aB)AotXeX~RwqQ>*GKUI9Gp`}G5I3(O&3GY2A`3)lMw`snKCkZLk_`i*~ab z?PfLF$!e5~7_b`Miq+0eR$+1BrrUzmZm!m~F}4^_R=e0@+)Y}r+MW44JvuD54OZi9 zae`HYIn!J%>$F<_Rhs zi{C~0{LLiDx33Fdt@-qWNhWu7~cpFU)X`IeKJ%Jjy=T| z0Zvi1i^y7OZrf@g#I+a*Qxs-ZZN%b;wW>9n7V83a#*S>Zr6`}YfJz7mC6u$2tQtt7j?FF4-0g>)#(OjfZ5b1v^(aU6DI z`w()Bz4AP+J3o7VR`^V$GYddl72jhUU%-loU$}ckV-wh#G#gPxL%*sqJb!WPYnPv? zmxF5YgV~MeM?{*WyHU$LhX#wQh1>Ot6TQxioZB>VZevMlWBL5XD(k6{)%8y7D7vfl zk6n7|QP^I`cg!iRoNG#DTV6Z>oAfa+#Rs+5o2%^om*Sx}|9({@I-R~6O(&+LSn4aasS5vY1;S6tjF;LoA9mRL(I?W!P|-M1SfXDfC~ z(CU}nu`$?6Dn=2a2-?y{C-%V0*ygF1v5js|T#&78y|I$rz5&5!hFhA3TN>xwcWQVk zijHmDQw{Ax_r|u{uI6Pudune0(Ac>*$``eGwSLv5hj$FWXDr*YYnXf(fsqS2eg?P4 zTNYZYC5zM!CjGjv^iz2=ToiSH60pC%w$yL{P>!0Oj{pTA9c7mNGY5YL#ne=J?}YITX*5)D1e8}a^Ho%e zAz!Hg@9WD=WtO7iit2LUS^5g=k6~U1;>dWGav+UxHt|5A#@B?yqha#_6-jXTlvGGX zv#PDuigJh6RN*X27ocfXDu9`6e?vSw+TiUiZ^%|QALWGWAHdQ1H((Yt*)Zc^jgnQ` zHFJ#JPQ`a$>wDG^bH>o2$BW4}TIMTnX-bP`n^0g`D ziCKOM`2c>kUN*0)^cF1)Y#X!)xND1)7A=C9=4i4p_--wvIGcix83xP#nMXt7LUBXt zxoujcv^MW|&0*|`qp8H79X>jY1Okj;amUhN`&V#m7@SugJ{r#U91BuLRCi*J9*<&Q z9gFlrr(aOrl|A#KG5*>cYp&QEt{ho$Y2(35PwtbHIE*X^<|4R{4SaJV>B!idX7U`n z{pK5WsdyVPNb9g<#8UH^lpe=>V#JEb!5eVt&<sVIf_$jBNr>@gpFoZM= zeS0~n%a2sNlo?3t`L0?Gci#0;!<~2il~@kc4(q9WH_C5Vn)2OL@ia}1cY2DaeYE*u%6GH; z@{Q&SOGLQ%`|6%tiBB`SNZGI;1A z%nK(CWKY8nC-)LkF6bTIp=IxUG;DyNDg3Yz+B@_}v&>?JX`rOoO;UOr(0L@Gpc*Tm zj2Ce#FLqy}KY5kaG?uI0fodW|C3<(GDVod&YIAjhig5Hsw&df;*yg^ph1lv0V0y97 zKK3DRu}dFU(02$Yf0FQqic>vl2-=|}Y=unka!0l$vTp+LOKWp9z{IB%rdp_1ys3PL`u^WkY6re~Za$s|)^e7u*|zHi@Bj zR|2-0WE?#{&9z?<^e6o;>+@F2{_OoDDYGq-fliXRvc9{>41W#=FYYu?zE26sEj#`U z6jn1f^!_}=k95-r^*qc+_>rz8j4$*fMsk=x?MFt`eFOS&_`ode;s;N2L`sS4#_w3e z3r>kg{f}1P14QNvzmZ<%sM< z5(_PK zXE49vPtxcWmEC9{D!p${=s%n^B3&tv_lPFd{1Krj$N zCW1i-MEbxGzB-2VB17#*Vn{wAGq`sg`5W2E562Nl5*`xDeU#@~K@hl=M&JV@@*U6D zeg^UQZ_g85lvIefAI4f^v4rP-(l`W@_)qa9K6Mn9@gPw?Z!f^oJqW~lVmy{6Aozx7 zLy*62GL|O-aClqFN~)?bmqe_oaoa1F*zy9a`5~^q9p0eJ$>Si~Sd^wojHh_=@@G1hW+2$bX#&aBBI+tUG=qI^h+dwj z!r@*`M}Dk3jpC~lh%Xs$e=32@^3kB?q_aPp6u?tiV%@k3i^^gQUCH6?qc+!{rggWFWD)<&2FzliF3I(0{Ym9coDLo?~&St4b}Fx=P7Izi1+;SZNXh zp?$ZCg4$QnDy-hDB8Ly~S0rbbx7JP()IToQMS1yO3 zq~zKx(m&2UKtwm;l%RR7id#``$^yqS+rBf4Y*+fCTN*mvX|22{mrNxY{Iy(C+OOTt zIk#tmBvNotOAn8|6Q7YsMv=$uyYk3Dm2-gc|KyV|L2DljCrxcSF+of?F=2bp@?#@N zpAKypsB3>CsLQT`$BZOl(a%Ch96k%?!pi|guy;2}TT$dDA$PIuk&_Oq;RYlfhVzF; z!m9#r*td=(SNvRDJ~EXg2cRD)5RO1vf~{xp<5NLRbGi4uWFjf&MfZ}=HL}09=S(Bp zD4ECqIfHc4mSXKP0K6=ggsXa|O65Ggh#U;5zz3BG<|7auQ{ulAk&Bvqyivt3&LsAk zZ8(HBT>);AWb43CqC8@Sod_Q<+uh74v`3B0al3jhN6*ghm{GuHAQ0l@+a6cmOdx zMR0QpA6-o1NF*;SCPV2pjs0)Mq??aU_S1-@H@{X!V#x~bS56M5wk0!RMpmRMtV#@= zFivKXusT_6t>(g35)p2&l?T+f?*ZHBxkHN~Sh;(m<6=tEgJJMP#82Mk#Q}dc$-=dH ztnXj2AFUwYsakaRRV%qLpuK)NLSW@mZZ3dj%A^wOT*&wmk!_EjCt}Z|>RhY;zmULB zC}JT2cL>960RN*H{3@MWj-o1^(6uZKzV;9ak)@D;Qs^W*_*d>H{-n`<;(qcD4a$HA z;~`-y3)Ps7AcsG*h)m6FW!cRd7T(a6f+oW1NX(u0gw8dJ-&aGT__!L6H5~|Tb9H443udgF%_D;3rAPErI zWN+$3c5N}~9{VBSDUjMh`8Ln2BbjiT{y-gBsrm@2a^nLe)P8LlnL_v@8{o;AB_t|H zEFoRfkQ4jyC8Ycw#Jxe5H!g)8K~MXa zOUWlxc6TTEs}GS4gOD1eI^I8P2iCK0U+F~k@E155#b%C=Tc166QG!9>@%Jqel%0IUQkE# zKymm%ZY=C|iXiWEfk`q;If!jA0t_@uc?g`=v*Srp7}N$euIFSLvD-^HS?Vd3_xO(+ zNO=TGMH0jd~DQ&If z<>mWZC@AccyoUb@FU6Ww^iIs#5BKg*b7z^>9r+0=UNO>#e|G&*rey(kr zbE*;)YZ3cm1SmLpRgP!GBIxG^^0q1&>bcza?3ciO2k@`|PNvyEeu*TiJtwgzUnY$p zt)`cW@xPW9I;{U%TAuQNW@2aA_H30ZcIj=++TL(j>N#nPc}h-$?nqhTvPj;zhxn0P z`&WC&KiV;x#}2~Y`_w^lpVLqzp7%F0MspbYQXS3L93gIp68=Z)p{J$EFQtCum`WYcijou@=HqQ>W^Epv{2fbzU-wm}_ z9wEb(Jnjt==xIuP;2Y!$*i!N#l9+!J>ZZ1>=XTYsH^cEyN$H)cmX{=50i`Qc@Q*_f zD%fm4cZk#y)%PBJ-K%WD(x-2dMDiLpy#Zb#_&C`K>>W8ylK+32`j0po|Hp+{mv&{K zn}Z?2J4GN{dv{qu`zj#5_x@W%Pu}Jrf~9=Q+vHdJJ!SiU54V@ULsV3E7P%6!+Y(-T zf)tE&hoyv{Lys+jdwG`Grs`6wyjc<6Zxl8a-p_~*5>3)|#wzx9=XmIQB)OZ282E@` zDaYZ&$Q4fg&LdVwEZ_PbiRCNbBfaT=wDu$Kk>_boF^gP9BHthwfB;>ZG!VgR$u;E4oVxJ{8aa-_mEP}-G2sZ%`Cw*>54<*^xkEHz12Vmhl<`jt} z3HH)c~28%0a{B#TH3eg|Hf5h=dfCSC@fXBSHA24ue8AVF3t7YGeoelplRJtc75#n;OKT7a_1#^~LXpl_i9{!b zmbPJQ)Q?-QcfM4FOXQl~g%a=0A=?|6k0bZnZ=WZ9fMGjmYBe3@i(-RnkZ&bR&ww}N zE?_ATK@fsq0M&TxdvV3K3y0RrQP6lP9~w&`SRs_FFOkl5VNfne^^@cOLLo-Cu94wz ztlX?_DH1OqMl4ZSibfEFAQpftywgg>aris|fe=9=mV_B6DPi*>jS&GJaK0xlkpl9R zJ?t{+hh2uxLD)zp`lkX|B8ypd5)u|#z%lLl>MvZn&Sk~m5MJnBuKk8|mZSbp;(;We z@D0Qv5AxD)NF7l4_8W)^nOYs#9|9Wq`iyWb;%K?~a9esGC4#FJ7cJ3Kf?SCgu*6tJ z`Ypnf{JC$zpB&{^z9o-3ZEx*=$d^>-(2mKiD4SI=xMCr%x=xbs!53yyX*C2JoavL7 z{=W3${-qM`sZ!|=*l9VfQd%zVRO6k?Pw%<1^m*yH`_853AGy40hx9VPe4X^83m`Ij zgADa{=IMp<${$H+GJHHlSz=x&Mw9S6@=m$=?My&iGmsl^kxtD1QXswN!>`;R(RI;q zU)a^<&GqmuwwxcEA+r-GA60il{kEYW2pyZv!y$MqdlFn70}GIagCRACUk-e7qXaVF zEJGxzLXw_9QLhK!@Q3rJT=}KJ;e6RmaA5U(%S{p^(iCL=yD9m*H%Z@Ep#=1rB7>O-q^zWTPN`+w+zPAf)n!EN z(22PUQk7tvD`8Es&qj7-PJ1cgRq#lTH0V!0eEd%kN{Hrrej*Wcppy3vqva~dJyY`m zw@EV5aMNuvFY#Tt<2A1%2yLx9EzIn_3+Ukw(#*8{+HG<_J9WaypZ=Nn@TvWXjz91- z(P?!eX-vOa$>+lxK3ZR!7v!n=IWN8Z#ifD2_cN*Hk9P#$RG3G#{D+Q^2L|6=(Nc53 z{gB7tPA$_3{QUz3)*$3m@I}qR<=)L)QgcW-F^2-7Fwd8v7O7Ih`PE-YOq56sK_e-U za9GR{-B2j9!Pa<(UrB;m`V;!b$NWn6dtn8A|Ti)GDQ-&@mh^FZ2cGXQC$VfY6fCe{=SQBT)XtkqLq%#fyrp+@Pc>F~WbNor@{eIq2nqG(gE?Zjl(| zIY83`^>Cf9RMI7WpC=@5E8d>5ci@hJrxN;cgNn`zP0fv09FN!L5#{kzzubY!;{(-D z=KFslq12DERqsY;m_0KjVGQmR%O+)t&2mH;WomJ zSK$q@C3(%j{MyeXjDVm1nS`?H6QOL|VXaaPb($u^aC=Nk^}K2@4d;v0)So{q zkth|sKoi0D=g?pZf4|Nlk@$CSIE}2cqPWD*>|+KV@kJA}ZlkJ2rl%%RVa0qU@pGDo zyB)k@*fKK|-jvYPY8Go2Y2?2i?@9PpX9E&B%@eJFOBy<`rx?6$OB2Jm8c$}Mtpw7Y z&Hy6f~2XguD45pzAmwdr5zQz6$mCVrdp z`ElEWwwIks9(XD`bLHq88by54ww!JAw;Q*QIThEfQ6K%ef9TqU+agc=^Hj>?UHo#hl*hBwc`8=8VySr?q6Fb53vmRDqd|3$ zU|0Eq7k^tvqx@#0Ja>aZpgaxyI~|=D33;-O;hBwt3L7)WG=`4-#4zr9n8J{H`}=TS zr>Cjmy21X6H~qDPyDQ%e_Z!?<`DSM|l#2;Jwg$$*D9S&wfshOjk`#OyR}HI2>21Eh zC(ZSL){0AE@iS8!<>~WCT)1+x(qnccW>u+onYLC*nCady@ThFRBbe_yMs@tNNSG}< z1wlzyOT%E+%ZUu)wmsl#8v|ivP7jC2S0U8O9g#$*^m5`?dcy?T7zr-=KL;R8yhw-t zz_7!Gg80F(>jJL9&6y9rH|#k~!HMWf{cF|W_>Ud{M_}-$zEQeb)ne5momu?#7>N4n z!Q9@9_DsL_#>&fk>MyQ&^75*cmp1Lb_Qoo)WvDg))k`lwcX8A93$MH;FWlm*ksI!B zpbz*ZAhKd&zFUxOQbmJdELsYxiD$VtahY(egV! zT=J)fdlsYy{KanA2q@)+ys*d_NXmt2iN)|YKvcN0o_>~DUF}P2$oTmk1 ze=JttGjPG~-JUF5; zy36(nd#CT1zN@IQOJ-x{l*Y)Zr-Sc>Wp_w6fjK)Q@=S1QQ*dfyr-5fWakPrw|xG&eOjO>sO;o0q6O-ZvpHR{2(}8p_3# z-}r_E+lvD!d@^D>^uW2sNy?Ul>vS&MVS>IGOyh#bK~GvQ(#5Fa145~RPx>1TB-{Cf z5E?hDd6jAeHZnZpWH5y_1^>x1SQrGvVlk zpK5mWuE3L6T6@FsgQa{9B$LT2G~wR)nh2UNN5{-cjwgWlDGUB<1T{cbwLX%5>)e(F zM$rirjsxe$z!Q-BqFCA?qTMwSU$-1~tx@st&4VkkG>~4<@!w);^x$Uc%lW)lpMCJk z9{4<#|6gp6yq(*lDk+msjicQ(@{))@7DvN_>Oo?jlbHltp4Xs~cs=^gh0g}q?WRM* zO^wfI=1JnbWM4y9>Um9(=zB5==L{g9B@FACnpf*yB{axdmX$(3j`hIdM#OF(J6 zQ)o|mMQ1;sLch@r7xEF^S%#=kEYgEmdI*8&|8gv?K(G>l*nVs76j*RKIs)vbs2km4 zxI>!0-DyIdFiRtLz`gMhMlE(E!p7SOIyfTagw`VW0@zw4CqK$7o>9P+D<~lh_AzAh z5d<{|5IY7X{1n1BI_Kp@e18f=MTBSkygT(H@%Ddrr&B2mhyB^X_NhJTlO)123-8d1?g+$8<-Gz<4r1f(ngm8E#i7O7zBB+*fxhWWi(o%J zq90vyuR}K`AHpxA+#fbY0!e^O+Jp>mMz93|B%ijFa2wBChJ9lD)4@TN5X`CahPL&J z%iiNpdykjo51rcA^rsoLmoq36STxIIEuK5hVjb&xrDg{e(Q7$215$KHA((MqB=^gt zoyb!>Yaoq+zT7*ImbqTU3~0tR+V!f;b1s22hj-jKHuL_O0>^|*YV06}RycM)0|7?q zrK1Sy0f1z1lOW~uO_?+ghSB*8kd zQr!j5MWS3@MfEJm+UV{(5 zeE?G9+B`uNp23|1^)P|zB4unfHVj7~&Su=l&mtJx)){nLnZb3d5W^Y-2NC=Y!6XES zk&=6v<^1-ca3xdmc`pR5j-*6hWD!2VgC04i`x=&}BM_wOpycp{EGPI>BIIQAw9zy{ zBd^v(O13;J$58wrX&m1$ng)>__LoP~Kh&Us-Q(z(_;rZuaR4m_7m6AaXKOl&a9q#v zF#qQG@>lwh5q#ZvI+!v@^?`S??Ee{0qp2%J(0>x`OP29rljt0ch#Ae}2PaWWW?N^u zZcPX!w34vMypxm7_{7Q7I8+P_5p`~slq1M2ADlUpYULz%aRg?=PN5itYmi;CaDay< zQ}}qE-ERs_P?8^c`ZQWJ!mV(jsVmS{jG&?T_K#Igwh-J3Mp2Qj*PjGGvyhfhP%(E* zqtWfX1T|VdI82=m!^&zOFr8lW4Qcj#62A5=tpeQAr#x#OokmQ2(>!`4;8W}b{HLgw zw|e9G`Er^?KI6Mfg;zOIN{d3@!{OXc&98WEnW(v;?Cv$`zkK;UA6QooJzl^9?mRyW(A$!Q+Ok>9zKlR9YNskZXRCxW1l@d zB0paN+F8tdR8o^hj!^Lpm9!!2C{{EgXf=$S`SI2B!H^G6p0`XK(+V!G55{Yt{`SxnXGZdQNzD^~vr6>8Fj@7S@J`tknrsV_0|!X+~1 zuLjX@`?pJIr8@g9M0E@RtS(I0OT3XvOZ?6C@5e^acta5qw-}-axZdzg<1{+zWZ8LU5+0l6yHade#6E>C+^<5 z9XP!4%xA8md5NfU*{tq7a+lr*95o3bhH(DXD(XYJ*}qvu!`0+A4|{@k;oTmidapO3 zPJ#3*pZEk#;U$mJoprw>WnlxYn8c$R!KA6Cq^ue?VJa2WY&9n2dEVjctmBNvJ&z)E z^V%xs3%gm9S}0XiS|lq!lmcPUqb~y3x0b3j#o(K&F7pr9(@=ZkS~^szx((HN_G1vC zNqd4CJ$vwR`^+~>*yJ)spZ2-;b5;7U*hdA`bP(+GPN6iAZ!0D$erP?_kvjg~dip)_ zwjX|y-cXSOzHcM_bkNTz;iCZLJ#zj1+0rj~OBfm&p!5F3Y7Tpsv(~kKRQi5>w%ZgUps!3=q#p_wD@2UOZ7HP7I&hMMH?M{ncHxJMk%iBmvyJ zrOxM}pVG9tDC}xy1nCHH!z5)R7=$1P!N&-)5ZplUI|4OuCutGr5%fne5J47#!3c&R z@JBEK!Afjq!crN6N(5E{^QE*Fiz0A??+Hq3?BY5s;cL^<0|@F6EJg4Tf<&y+0Rg@n zDs{$E8iI5L!3e~nI0{Q*jl3R9n-Oe7fUk^5uLG!);;@*2-~$9l5WJ1xG=lvIK1Xm4 z!B+_WiQoc)0|+o40Rr6TOIs1_K=3Sr7^H-YVabk{V%?8R1_|eO z=?etsm5OZW4i?endrTX`mnWmaB?8zXf51sLDqMz02rwU^Y7V!brEy-cb zOQ-lsN_F3xbGXctW$vgOZDenw;JFj6gNq~J$X9dl>sN}jCPms$ Qn&|^c#7zZ4zzmZ7KRXkX9{>OV diff --git a/ui/__pycache__/widgets.cpython-312.pyc b/ui/__pycache__/widgets.cpython-312.pyc index 6a4fc60a2a1e3ea2672fae5385a891858c142486..006a12cce5d6b0cf5c7f2505a436618808e81373 100644 GIT binary patch delta 853 zcmX|-ZAepL6vywG+swHs(lw^jt8V5tQFjFmCcaFj<`iFoAcO*i4L4FJH}!)lnpqkq zsK?igO2S^~%j6O$l_3&JVyUE=;fFLNg1};as4sTUg6@ak^MB6sd(OT0ERMsg3DB%) zG%7}VR(%uh!Wqq?N_S+CwidUy-eqa>)Vc)A!D!FH)I&?*S&O)#>W#qT#1I(8b6Oir z;$Q-YK5ZO$a82vN;@C`R!~40B$R)9?Q`Q#rOh(Na2AJEI2dg8yxpdCvMS<36uhY0Q1XBdMgq9O4@ z(8(^TQf^iY-EyI+Py=IbwW%hYXhRfnBRyCw~Ly?M7I|1G@>FVtBzcz)gH! zxCWmvJUJ34ixNZnLGnIJGUnvy#QmaUNM5F^nS?3{J7JzHWnQPNu0pW8J#|juEuE|o zMo{hGVHtBAdf3Bihd~-^x8r?eV1lb;2!v+BUf^dJDH77Lufz(A_@-n#Aus_dK4pYF z5^^Q{IgwrObvDz_zUFu0I362euh?mv8)lmJaF}hp*7Iv EFTM2xIsgCw delta 797 zcmX|-T}V@57{~XVd5(GPM3#=!?VL@gZr0{REkE)rX0n`3M5rLch>?|L9Y091oPCIf z5cUYInKdf$I?jbq6fr4DjqE}Yp^H^Fk&uKJ5fZ)62E7+Q{^x)G|Mz)$UOj-hAV`an zBwTEfrmV;GyN8%_)V^K4Nqw5i{x|`3PZSPh`&}Zv_7dU0j(W15h&{jVeriVwn|S!GP42hpm1mThCY&c+AkcHvtAq*sQnoK=mSik=g;XU&m%w1A z6Hu$je1k>hcCcV;`Hb)~ZiJobVvrR2x3Y>x^}rjg}otW+C|ksfgJ=R!9T&cHRy_?>>ftp`*0ez%Zq;04?5|bepcbkf}gl%pcoY6+z(*U=z;P5i6-m5PY#@GyXO{=aqaWIdq9y$3vl@%uUI%D^pmS3f6Pt(dq1W;cPo3h* diff --git a/ui/property_panel.py b/ui/property_panel.py index 12bdfbee..27bd8632 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -3,10 +3,11 @@ from types import new_class from typing import Hashable from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, - QTreeWidget, QTreeWidgetItem, QMenu,QCheckBox) + QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget) from PyQt5.QtCore import Qt from deploy_libs.unicodedata import normalize -from panda3d.core import Vec3, Vec4, transpose +from direct.actor.Actor import Actor +from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib class PropertyPanelManager: @@ -80,7 +81,7 @@ class PropertyPanelManager: elif model: self._updateModelPropertyPanel(model) # 显示脚本属性 - self._updateScriptPropertyPanel(model) + # self._updateScriptPropertyPanel(model) # 强制更新布局 if self._propertyLayout: @@ -176,6 +177,7 @@ class PropertyPanelManager: zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v)) self._propertyLayout.addRow("缩放 Z:", zScale) + self._addAnimationPanel(model,model) self._addSunAzimuthPanel() material_title = QLabel("材质属性") @@ -184,6 +186,7 @@ class PropertyPanelManager: self._updateModelMaterialPanel(model) + def updateGUIPropertyPanel(self, gui_element): """更新GUI元素属性面板""" gui_type = gui_element.getTag("gui_type") @@ -634,6 +637,13 @@ class PropertyPanelManager: def _updateModelMaterialPanel(self,model): """模型材质属性""" + if model.is_empty(): + print("警告: 无法在空的 NodePath 上查找材质") + no_material_label = QLabel("无材质") + no_material_label.setStyleSheet("color: gray;font-style:italic;") + self._propertyLayout.addRow("材质:", no_material_label) + return + materials = model.find_all_materials() if not materials: @@ -721,13 +731,13 @@ class PropertyPanelManager: b_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'b', v)) self._propertyLayout.addRow("基础颜色 B:", b_spinbox) - #Alpha分量(透明度) - alpha_spinbox = QDoubleSpinBox() - alpha_spinbox.setRange(0.0, 1.0) - alpha_spinbox.setSingleStep(0.01) - alpha_spinbox.setValue(base_color.w) # Alpha是Vec4的w分量 - alpha_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'a', v)) - self._propertyLayout.addRow("透明度 (Alpha):", alpha_spinbox) + # #Alpha分量(透明度) + # alpha_spinbox = QDoubleSpinBox() + # alpha_spinbox.setRange(0.0, 1.0) + # alpha_spinbox.setSingleStep(0.01) + # alpha_spinbox.setValue(base_color.w) # Alpha是Vec4的w分量 + # alpha_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'a', v)) + # self._propertyLayout.addRow("透明度 (Alpha):", alpha_spinbox) else: # 如果无法获取或创建基础颜色,显示提示 no_base_color_label = QLabel("无法获取材质基础颜色") @@ -820,10 +830,10 @@ class PropertyPanelManager: metallic_button.clicked.connect(lambda checked,mat=material:self._selectMetallicTexture(mat)) self._propertyLayout.addRow("金属性贴图:",metallic_button) - #IOR贴图 - ior_button = QPushButton("选择IOR贴图") - ior_button.clicked.connect(lambda checked,mat = material:self._selectIORTexture(mat)) - self._propertyLayout.addRow("IOR贴图",ior_button) + # #IOR贴图 + # ior_button = QPushButton("选择IOR贴图") + # ior_button.clicked.connect(lambda checked,mat = material:self._selectIORTexture(mat)) + # self._propertyLayout.addRow("IOR贴图",ior_button) # # 视差贴图 # parallax_button = QPushButton("选择视差贴图") @@ -835,10 +845,10 @@ class PropertyPanelManager: # emission_button.clicked.connect(lambda checked, mat=material: self._selectEmissionTexture(mat)) # self._propertyLayout.addRow("自发光贴图:", emission_button) # - # 环境光遮蔽贴图 - ao_button = QPushButton("选择AO贴图") - ao_button.clicked.connect(lambda checked, mat=material: self._selectAOTexture(mat)) - self._propertyLayout.addRow("AO贴图:", ao_button) + # # 环境光遮蔽贴图 + # ao_button = QPushButton("选择AO贴图") + # ao_button.clicked.connect(lambda checked, mat=material: self._selectAOTexture(mat)) + # self._propertyLayout.addRow("AO贴图:", ao_button) # # 透明度贴图 # alpha_button = QPushButton("选择透明度贴图") @@ -894,8 +904,10 @@ class PropertyPanelManager: new_color = Vec4(current_color.x, value, current_color.z, current_color.w) elif component == 'b': new_color = Vec4(current_color.x, current_color.y, value, current_color.w) - elif component == 'a': # Alpha分量处理 - new_color = Vec4(current_color.x, current_color.y, current_color.z, value) + # elif component == 'a': # Alpha分量处理 + # self._updateMaterialTransparency(material, value) + # return + #new_color = Vec4(current_color.x, current_color.y, current_color.z, value) else: print(f"未知的颜色分量: {component}") return @@ -939,6 +951,24 @@ class PropertyPanelManager: except Exception as e: print(f"更新材质基础颜色失败: {e}") + def _updateMaterialTransparency(self,material,alpha_value): + try: + from panda3d.core import Vec4 + if hasattr(material,'emission'): + material.emission = Vec4(3,0,0,0) + print("设置透明着色器模型") + if hasattr(material,'shading_model_param0'): + material.shading_model_param0 = alpha_value + print(f"设置透明度参数{alpha_value}") + if hasattr(material,'base_color'): + current_color = material.base_color + material.base_color = Vec4(current_color.x,current_color.y,current_color.z,alpha_value) + print(f"更新基础颜色透明度{alpha_value}") + self._invalidateRenderState() + print(f"材质透明度已更新:{alpha_value}") + except Exception as e: + print(f"更新材质透明度失败: {e}") + def _updateMaterialRoughness(self, material, value): """更新材质粗糙度(安全版本)""" try: @@ -2823,7 +2853,7 @@ class PropertyPanelManager: from panda3d.core import Vec4 # 安全地获取当前 emission 值 - current_emission = Vec4(0, 0, 0, 0) + current_emission = Vec4(0, 1.0, 0, 0) if hasattr(material, 'emission') and material.emission is not None: current_emission = material.emission @@ -2835,7 +2865,7 @@ class PropertyPanelManager: print("设置透明着色模型...") # 设置默认透明度值 - default_opacity = 0.8 # 设置为较高的值,确保模型可见 + default_opacity = 0.5 # 设置为较高的值,确保模型可见 # 同时在emission.y和base_color.w中设置透明度值 # emission.y可能被RenderPipeline的某些部分使用 @@ -2845,7 +2875,7 @@ class PropertyPanelManager: self._updateMaterialAlphaForTransparency(material, default_opacity) # 应用透明渲染效果 - self._applyTransparentRenderingEffect() + #self._applyTransparentRenderingEffect() print(f"透明着色模型设置完成") print(f" - emission.x = {model_index} (透明着色模型)") @@ -2871,7 +2901,7 @@ class PropertyPanelManager: # 不透明度滑块(避免混淆,使用不透明度) opacity_spinbox = QDoubleSpinBox() - opacity_spinbox.setRange(0.1, 1.0) # 最小值0.1,避免完全消失 + opacity_spinbox.setRange(0.0, 1.0) # 最小值0.1,避免完全消失 opacity_spinbox.setSingleStep(0.01) # 安全地获取当前不透明度值(从基础颜色的Alpha通道) @@ -2879,7 +2909,7 @@ class PropertyPanelManager: try: base_color = self._getOrCreateMaterialBaseColor(material) if base_color is not None: - current_opacity = max(0.1, base_color.w) # 从Alpha通道获取,确保不小于0.1 + current_opacity=base_color.w except Exception as e: print(f"获取当前透明度失败,使用默认值: {e}") @@ -2891,15 +2921,7 @@ class PropertyPanelManager: """更新不透明度值(同时更新emission.y和base_color.w)""" try: from panda3d.core import Vec4 - - # 确保不透明度值在合理范围内 - opacity_value = max(0.1, min(1.0, opacity_value)) - - # 同时更新emission.y(可能被RenderPipeline使用) - current_emission = Vec4(0, 0, 0, 0) - if hasattr(material, 'emission') and material.emission is not None: - current_emission = material.emission - + current_emission =material.emission or Vec4(0, 0, 0, 0) new_emission = Vec4(current_emission.x, opacity_value, current_emission.z, current_emission.w) material.set_emission(new_emission) @@ -2931,25 +2953,35 @@ class PropertyPanelManager: elif hasattr(material, 'setDiffuse'): material.setDiffuse(new_color) + self._applyTransparentRenderingEffect() + print(f"材质基础颜色Alpha已更新为: {transparency_value}") except Exception as e: print(f"更新材质Alpha通道失败: {e}") def _applyTransparentRenderingEffect(self): + from panda3d.core import TransparencyAttrib """为当前选中的模型应用透明渲染效果(简化版本)""" try: current_item = self.world.treeWidget.currentItem() if current_item: model = current_item.data(0, Qt.UserRole) if model: - print(f"正在为模型 {model.getName()} 应用透明渲染效果...") - - # 只使用最基本的透明度设置,避免冲突 - from panda3d.core import TransparencyAttrib - - # 启用Alpha混合 model.setTransparency(TransparencyAttrib.MAlpha) - print(" - 透明度混合: 已启用 (MAlpha)") + + self.world.render_pipeline.set_effect( + model, + "effects/default.yaml", + { + "render_gbuffer":True, + "alpha_testing": False, + "normal_mapping": True, + "render_shadow": True, + "render_envmap": True, + }, + sort=100 + ) + # 让RenderPipeline自动处理透明材质 # 当emission.x=3时,RenderPipeline会自动设置正确的渲染参数 @@ -3880,31 +3912,194 @@ class PropertyPanelManager: print(f"⚠️ 文件通信失败: {e}") return False - # def _controlLocalDaytimeManager(self, azimuth_degrees): - # """尝试控制本地的RenderPipeline daytime_mgr""" - # try: - # # 检查RenderPipeline的daytime_mgr - # if hasattr(self.world, 'render_pipeline') and hasattr(self.world.render_pipeline, 'daytime_mgr'): - # daytime_mgr = self.world.render_pipeline.daytime_mgr - # print("✅ 找到 render_pipeline.daytime_mgr") - # - # # 尝试设置sun_azimuth - # if hasattr(daytime_mgr, 'sun_azimuth'): - # daytime_mgr.sun_azimuth = azimuth_degrees - # print(f"☀️ 本地Sun Azimuth已设置为: {azimuth_degrees}°") - # return True - # elif hasattr(daytime_mgr, 'set_sun_azimuth'): - # daytime_mgr.set_sun_azimuth(azimuth_degrees) - # print(f"☀️ 本地Sun Azimuth已设置为: {azimuth_degrees}°") - # return True - # else: - # print("🔍 本地daytime_mgr可用属性:") - # for attr in dir(daytime_mgr): - # if 'sun' in attr.lower() or 'azimuth' in attr.lower(): - # print(f" • {attr}") - # - # return False - # - # except Exception as e: - # print(f"⚠️ 控制本地daytime_mgr失败: {e}") - # return False + def _addAnimationPanel(self,originmodel,filepath): + try: + model = Actor(filepath) + model.reparentTo(self.world.render) + + + model.setPos(0,0,0.3) + + model.hide() + animations = model.getAnimNames() + + if animations: + animation_title=QLabel("动画控制") + animation_title.setStyleSheet("color:#6B6BFF;font-weight:bold;font-size:14px;margin-top:10px;") + self._propertyLayout.addRow(animation_title) + + self.animation_combo = QComboBox() + self.animation_combo.addItems(animations) + self._propertyLayout.addRow("动画名称:", self.animation_combo) + + #播放控制按钮 + button_layout = QHBoxLayout() + + self.play_button = QPushButton("播放") + self.play_button.clicked.connect(lambda:self._playAnimation(model,originmodel)) + button_layout.addWidget(self.play_button) + + self.pause_button = QPushButton("暂停") + self.pause_button.clicked.connect(lambda:self._pauseAnimation(model,originmodel)) + button_layout.addWidget(self.pause_button) + + self.stop_button = QPushButton("停止") + self.stop_button.clicked.connect(lambda:self._stopAnimation(model,originmodel)) + button_layout.addWidget(self.stop_button) + + self.loop_button = QPushButton("循环") + self.loop_button.clicked.connect(lambda:self._loopAnimation(model,originmodel)) + button_layout.addWidget(self.loop_button) + + button_widget = QWidget() + button_widget.setLayout(button_layout) + self._propertyLayout.addRow("控制:", button_widget) + + self.speed_spinbox = QDoubleSpinBox() + self.speed_spinbox.setRange(0.1,5.0) + self.speed_spinbox.setValue(1.0) + self.speed_spinbox.setSingleStep(0.1) + self.speed_spinbox.valueChanged.connect(lambda v:self._setAnimationSpeed(model,v)) + self._propertyLayout.addRow("播放速度:", self.speed_spinbox) + else: + no_anim_label = QLabel("此模型无动画") + no_anim_label.setStyleSheet("color:#888888;font-style:italic;") + self._propertyLayout.addRow("动画:",no_anim_label) + except Exception as e: + print(f"添加动画面板失败: {e}") + + def _detectAnimations(self, model): + """检测模型中的动画""" + try: + from direct.actor.Actor import Actor + + if not isinstance(model, Actor): + model = self._convertToActor(model) + if not model: + print("无法转换为Actor") + return [] + print(f"模型已转换为Actor:{type(model)}") + animations = [] + + animations = model.getAnimNames() + + print(f"检测到的动画: {animations}") + return animations + + except Exception as e: + print(f"检测动画失败: {e}") + return [] + + def _convertToActor(self, model): + """将NodePath转换为Actor,自动获取模型路径""" + try: + from direct.actor.Actor import Actor + import os + + model_path=model.filepath + if model_path: + actor = Actor(model_path) + actor.reparentTo(self.world.render) + print(f"{model_path}") + print("转化actor成功") + return actor + + except Exception as e: + print(f"转换为Actor失败: {e}") + return None + + def _getModelPath(self,model): + import os + model_root = model.find("**/+ModelRoot") + if not model_root.isEmpty(): + model_root_node = model_root.node() + if hasattr(model_root_node,'get_fullpath'): + fullpath = model_root_node.get_fullpath() + if fullpath and not fullpath.empty(): + return str(fullpath) + tag_path = model.getTag("original_path") + if tag_path: + return tag_path + + return None + + def _playAnimation(self, model,originmodel): + """播放动画""" + try: + print(f"=== 动画播放调试信息 ===") + print(f"模型类型: {type(model)}") + orginPos = originmodel.getPos() + model.setPos(orginPos) + model.show() + originmodel.hide() + + if hasattr(self, 'animation_combo'): + anim_name = self.animation_combo.currentText() + print(f"播放动画: {anim_name}") + self._initActorModel(model,originmodel) + + # 使用Actor的标准方法播放动画 + model.play(anim_name) + + # 验证播放状态 + # control = model.getAnimControl(anim_name) + # if control and control.isPlaying(): + # print(f"✓ 动画播放成功: {anim_name}") + # else: + # print(f"❌ 动画播放失败: {anim_name}") + + except Exception as e: + print(f"播放动画失败: {e}") + import traceback + traceback.print_exc() + + def _pauseAnimation(self,model,originmodel): + try: + if hasattr(model,'stop'): + self._initActorModel(model, originmodel) + originmodel.hide() + model.show() + model.stop() + print("动画已暂停") + except Exception as e: + print(f"暂停动画失败:{e}") + + def _stopAnimation(self,model,originmodel): + try: + if hasattr(model,'stop'): + self._initActorModel(model, originmodel) + originmodel.show() + model.hide() + model.stop() + print("动画已停止") + except Exception as e: + print(f"停止动画失败:{e}") + + def _loopAnimation(self,model,originmodel): + try: + if hasattr(self,'animation_combo'): + self._initActorModel(model, originmodel) + anim_name = self.animation_combo.currentText() + if anim_name and hasattr(model,'loop'): + model.show() + originmodel.hide() + model.loop(anim_name) + print(f"循环播放动画:{anim_name}") + except Exception as e: + print(f"循环播放动画失败:{e}") + + def _setAnimationSpeed(self,model,speed): + try: + if hasattr(self,'animation_combo'): + anim_name = self.animation_combo.currentText() + if anim_name and hasattr(model,'setPlayRate'): + model.setPlayRate(speed,anim_name) + print(f"设置动画速度:{speed}") + except Exception as e: + print(f"设置动画速度失败:{e}") + + def _initActorModel(self,model,originmodel): + model.setPos(originmodel.getPos()) + model.setScale(originmodel.getScale()) + model.setHpr(originmodel.getHpr()) + diff --git a/ui/widgets.py b/ui/widgets.py index 0a9b4479..70347ec1 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -150,6 +150,7 @@ class CustomPanda3DWidget(QPanda3DWidget): filepath = url.toLocalFile() if filepath.lower().endswith(('.egg', '.bam', '.obj', '.fbx', '.gltf', '.glb')): self.world.importModel(filepath) + self.world.addAnimationPanel(None,filepath) event.acceptProposedAction() else: event.ignore()